7 ms·
My experience with out of memory is that in every single language and environment I worked on in the past, once an application hits that condition, there is ver
by lmilcin 5y ago
My experience with out of memory is that in every single language and environment I worked on in the past, once an application hits that condition, there is very little hope of continuing reliably.
So if your aim is to build a reliable system, it is much easier to plan to never get there in the first place.
Alternatively, make your application restart after OOM.
I would actually prefer the application to just stop immediately without unwinding anything. It makes it much clearer as to what possible states the application could have gotten itself to.
Hopefully you already have designed the application to move atomically between known states and have mechanism to handle any operation getting interrupted.
If you did it right, handling OOM by having the application drop dead should be viewed as just exercising these mechanisms.
- SigmundA 5y agoI am not sure how you can make a user friendly application like that. How about an image viewer that tries to open too large an image, should it just crash when it OOMs? I would much prefer an error dialog and the program continues to run.
- lmilcin 5y agoUsers are aware things stop working when memory is exhausted. As a user I am ok with this. I don't let my memory end and would prefer that the application worked reliably while there is still memory. What I don't like is applications trying to pretend nothing happened but then doing some strange shit. I have seen, for example, applications using 100% cpu after they hit OOM. Yes they survived. No, it is not user friendly.
- SigmundA 5y agoWhy would users be aware what happened? Did you tell them? This is a very reliable and easy for the user and other developers to understand. try { image = LoadImage(path); } catch(OutOfMemoryException e) { Msgbox("Cannot load image it is too large for available memory"); } That is way more friendly than a program crash and allow the user to try again perhaps with a smaller version of the image because they accidentally picked the high res version or something. Either way you may get 100% CPU as the program crashes and memory gets reclaimed, or the program continues to run and the garbage collector reclaims.
- XorNot 5y agoAlthough you've encoded the whole problem right there: do you even have enough memory to setup a MsgBox widget or whatever that is?
- SigmundA 5y agoIn a GC'd language which is what I am used to, the GC will pause execution and reclaim on the next allocate if there isn't any available memory cleaning up whatever was allocated on the attempted image load. After that if there is still not enough memory for the simple message box then you have an uncaught exception and the program crashes and you're just back to the no catch approach, nothing lost. Most likely though there will be enough memory for the message and you have a much more friendly result. I have used this approach before and it is way way more friendly then a crash with users potentially losing work because they accidentally picked the wrong file.
- lmilcin 5y ago> After that if there is still not enough memory for the simple message box (...) That's exactly the point. Once memory is exhausted, you can't take any action reliably. You don't build reliable applications by making mechanisms like: "ok, if memory ends lets design it to show a box to the user, we have 50% chance this succeeds". It would be better to wrap your application in a script that detects when the application quit and only then shows a message to the user. People designing things like you drive me crazy. They come up with a huge number of contingencies that just don't work in practice when push comes to shove. Stuck in a loop trying to show a widget, using 100% of CPU and preventing me to do any action? I prefer simpler mechanisms that work reliably.
- dnautics 5y agoAll critical paths, should be allocated on application start, and you never have to worry about that.
- 5y ago
- ectopod 5y agoFor big infrequent allocations (like your example of loading a huge image) it is easy to use a non-default allocator that returns an error rather than aborting. As the article notes, this is all moot if you are running on Linux though. The allocation will always succeed but if it's too big the kernel will start killing processes, quite possibly including your image viewer.
- beached_whale 5y agooom handling on linux is a system option and there are also ulimits
- alerighi 5y agoIn Linux allocations can fail in some situation, for example in case it's set a resource limit on memory, because it was set a memory rlimit for the process, or the cgroup that contains the process runs out of memory (so if you run the program in a container with constrained memory).
- tooltower 5y agoYou can look at the SQLite codebase for good examples of this. Essentially, you will have to introduce a fixed upper bound for the file size you are willing to handle, given your minimum system requirements. This way, you can actually test that your program gracefully handles OOM conditions. This page explains more on this: https://sqlite.org/limits.html https://sqlite.org/limits.html
- SigmundA 5y agoFixed upper limits are helpful but do not prevent OOM. Perhaps the system has very little free memory due to other programs or limited hardware or other reasons but your program is intended to load large files on systems that have the memory so the upper limits are pretty high. It's takes very little memory to show the user an error message it will almost always succeed even if the operation that triggered failed due to OOM.
- tooltower 5y agoOf course, it's not a guarantee. But if your program is under heavy pressure from other programs, it won't continue to run in any reasonable sense either, even without Linux's OOM-killer. Yes, you can show an error message, but then the only safe thing to do is quit. You won't even be able to clean up any on-disk swap files properly, let alone handle user interactions. Handling this is hard in desktop applications. For servers, you can have known workloads with better limits on each process.
- kragen 5y agoThat is what Linux's OOM killer does, yes. In Linux you can handle the problem by spawning off a subprocess and watching to see why it dies. Not an acceptable result for your antilock braking system firmware.
- sharkbot 5y agoIn this case (cybersecurity application), there is a complicating factor: you cannot trust that the environment is trustworthy. The out of memory condition may have been caused by malware to disable/degrade the antivirus sensor. For example, the malware allocates just enough memory that further OS allocations fail, but it has enough memory to accomplish its task then terminates to allow normal system processing to resume. Under that scenario, the antivirus sensor should be able to take some action (log, likely) that a malloc failed, and possibly even try to recover memory and identify the risk. But beyond that narrow use case, you’re right.
- amelius 5y agoThat's a design philosophy called "crash-only software". https://en.wikipedia.org/wiki/Crash-only_software https://en.wikipedia.org/wiki/Crash-only_software
- lmilcin 5y agoI did it at least once for a credit card terminal. The application saved state to internal database, and even if you cycled power it would just come back to the same screen and same state it was before power cycle. This wasn't to deal with memory problems (in fact, it had no dynamic memory allocation at all) but rather to deal with some crappy external devices and users that would frequently power cycle things if it didn't progress for more than couple of seconds.
- IshKebab 5y agoSo now if your code gets into a broken state users have no way to fix it. Awesome.
- lmilcin 5y agoDon't make it possible to get your system into broken state? Why everybody assumes every developer doesn't know what they are doing just because most of them don't?
- notriddle 5y agoBecause most developers also think they’re one of the rare exceptions.
- IshKebab 5y agoWhy do so many developers assume that they are the perfect ones who never make mistakes? They think that their programs have no bugs despite the overwhelming evidence that all programs have bugs. But no. Not theirs. Literally all other programmers are idiots and only they write perfect code.
- simcop2387 5y agoBuild perl with -DUSEMYMALLOC and -DPERL_EMERGENCY_SBRK, then you can preallocate a buffer by doing $^M="0" x 65536; then you can trap the out of memory condition with the normal facilities in language and handle it appropriately (mostly letting the big data get deallocated, or exiting). Then you can continue on just like normal. It's a weird setup and I don't think I've run into any other language with that built in.
- zbentley 5y agoUseful, but on Linux it's highly likely that by the time you're comparing $@ to ENOMEM the OOM killer has already awoken and is heading your way.
- Xylakant 5y agoThe interesting thing is that the OOM killer doesn’t always go for the program that triggered the OOM. It may also decide to kill another memory-hungry process (cough database) on the machine unless you explicitly tweaked it.
- klyrs 5y agoIf a program allocates all available memory, and systemd then hits OOM on a 1kB allocation, do you think we should kill systemd?
- zbentley 5y agoI mean, the OOM killer's heuristics are byzantine to be sure. However, if your program is not likely to be the "true" culprit of memory exhaustion, there are better tools at your disposal than ballast pages--cgroups and tunable oomkillers like earlyoom (https://github.com/rfjakob/earlyoom https://github.com/rfjakob/earlyoom). On the other hand, if you are likely to be identified as the culprit, I think the best you can hope for is getting some cleanup/reporting in before you're kill-9'd.
- Xylakant 5y ago
- zbentley 5y agoYou can increase the probability of being able to survive an OOM condition long enough to emit diagnostic info in most memory-managed languages (java, python, etc.) fairly easily. Allocate (and write data to) some global fixed size chunk of ballast memory at program startup, and whenever an allocator failure is detected immediately free that block and emit whatever diagnostics you need. simcop2387 pointed out that Perl even supports this as an interpreter setting; in other languages I've done it manually. This is more useful than OOM'd programs silently disappearing, but isn't foolproof (that's why I said "increase the probability" rather than "guarantee"): if the OOM killer gets invoked before your telemetry, if you've forked, or if whatever your language does in order to even detect an allocator error is itself memory-costly (looking at you, Python--why on earth would you allocate in order to construct a MemoryError object?!), you will still go down hard. Other than going back in time and reversing Linux's original sin (allocation success is a lie), swap is the "solution" to these situations, but for many people that cure is worse than the disease. I often wish there was a generally-available way to map memory to files (rather than mmaping files to memory) selectively in my programs. For cases like this--handling OOM conditions and doing cleanup/reporting before turning out the lights, when the OOM condition was caused either by code in my program outside of my control or other programs on the system outside of my control--having a "all allocations after this instant should occur in a file on disk, not memory" bit to flip would be nice. I don't need my cleanup to be fast; if this is happening I'm already on a sad enough path that I'm happy to trade shutdown/crash performance for fidelity of diagnostic data and increased probability of successful orderly cleanup. Of course, not being an OS developer, I assume this is impossible for reasons outside of my understanding; the few complicating factors I can think of (copy-on-write pages, for example) are hard enough, and I'm sure there are others.
- jbverschoor 5y agoYeah please don’t.. memory isn’t cheap when everybody does this. This behavior is the reason Java had such a bad time with sysops. Just make use of proper libraries that will invalidate caches if needed, or simply refute to spawn new threads/actors/processes. I’d rather have some application halt/pause or simply crash if it ran out of memory.
- pornel 5y agoI have the same experience in C applications, but not in Rust. Rust's problems are IMHO purely self-imposed from unnecessarily nihilistic assumptions. • There's no problem of "untested error paths". Rust has automatic Drop. Cleanup on function exit is written by the compiler, and thus quite dependable. Drop is regularly exercised on normal function exits too. • Overwhelming majority of Drop implementations only free data, and don't need to allocate anything. Rust is explicit about allocations, so it's quite feasible to avoid them where necessary (e.g. if you use a fat error type that collects backtraces, that can bite on OOM. But you can use enums for errors, and they are plain integers). • Probability of hitting OOM is linearly proportional to allocation size, so your program is most likely to hit OOM when allocating its largest buffers. This leaves a lot of RAM left to recover from that. It's basically impossible to exhaust memory up to the last byte. Crashing may be fine for tiny embedded software or small single-threaded utilities. However, crashing of servers is expensive, and may not even work. When you crash, you kill all the requests that were in progress. This wastes work already done by other threads, and you're not making progress. When clients retry, you're likely to get the same set of requests that lead to OOM in the first place, so you may end up crashing and restarting in a loop forever. OTOH if you detect OOM and reject one offending request, you can keep making progress with other smaller requests.
- lmilcin 5y agoCompiler can only do limited number of things and I am not talking about it. Think in terms of trying to save the file you are working on before the application exits. Or notify cluster map that the node is not going to be available to process requests. How does compiler factor in it?
- pornel 5y agoYou don't exit. You work with fallible functions, and keep trying to handle errors for as long as possible. Each function can fail, and when it fails, you propagate the error up to a point where the whole failing task can be gracefully cancelled. e.g. if user invokes a "Print" command, and it runs out of memory, then instead of immediately crashing and burning, you can try to report "Sorry, print failed". That too will be handled fallibly, so if `message("Sorry…")?` fails, then you proceed to plan B, which may be log, then save and quit. If these fail, then finally crash and burn. But chances are that maybe print preview needed to allocate lots of memory, and other functions don't need as much, so your program will survive just by aborting that one operation.
- grishka 5y agoIt depends on the application. On Android, for example, I don't want my app to crash with an OOM while trying to load an image. I want to instead clear my in-memory cache and try again with more memory available. So I wrapped the code that loads images into a try-catch that catches OutOfMemoryError and it worked wonderfully.
- derefr 5y agoThe ability to "handle" OOM in userspace, exists for a particular class of software — software that is usually configured to 1. use unbounded and unpredictable amounts of memory, where 2. requests are entirely-sandboxed in their resource usage, but within the software itself, rather than at the OS level. Basically there's only one kind of software in this class: DBMS software. DBMSes want to be able to try to process ridiculous queries if users ask them to; and then fail in a way that only affects the processing of that query, rather than the stability of the DBMS as a whole. And they also mostly can't afford the overhead of pre-calculating just how ridiculous a query will be, before attempting it; because that calculation often requires effectively 90% of the work involved in actually running the query. For every other type of software, letting the OS handle the OOM (by killing your process) — and setting up your higher-level inter-process / inter-node architecture to be resilient to that — is the sensible approach.
- mlindner 5y agoWhy are you ignoring the obvious examples of operating systems? An Operating System doesn't and shouldn't crash if you run out of memory. Additionally I have personal experience with network appliances (firewalls/deep packet inspection/etc). If such a device runs out of memory, it degrades gracefully (unless there's a bug) and starts shedding connections rapidly to avoid running out of memory. Such devices can't just restart if they run out of memory as that would be a network outage and can often be exploited to cause a more serious denial of service attack. Rust is a system's language. Handing out of memory conditions is par for the course for systems programming.