[HN Gopher] In C, how do you know if the dynamic allocation succ...
       ___________________________________________________________________
        
       In C, how do you know if the dynamic allocation succeeded?
        
       Author : ibobev
       Score  : 40 points
       Date   : 2021-11-05 19:22 UTC (3 hours ago)
        
 (HTM) web link (lemire.me)
 (TXT) w3m dump (lemire.me)
        
       | dekhn wrote:
       | I once complained about malloc happily allocating memory that the
       | physical memory system couldn't satisfy (never let your hand
       | write a check your ass can't cash?) but the more experienced
       | programmer asked me if I'd heard of fractional reserve banking,
       | and if not, whether it bothered me too.
        
         | salawat wrote:
         | If you're smart, the answer is yes, over reliance on
         | statistical multiplexing scares the shit out of you, because
         | it's all fun and games till the check is due.
        
         | pjc50 wrote:
         | Swap space is the Federal Reserve of memory allocation.
        
           | [deleted]
        
         | lmilcin wrote:
         | Not the same thing.
         | 
         | malloc() can tell everybody it has the memory but when push
         | comes to shove the OS will have to admit overbooking.
        
           | underdeserver wrote:
           | TIL, unless you explicitly disable memory overcommit, it can
           | and will overcommit.
           | 
           | This is crazy to me.
        
             | lmilcin wrote:
             | It is not. It is actually very useful and advantageous for
             | many reasons.
             | 
             | The disadvantage is the pretty shitty failure mode.
             | 
             | But in practice, I have never seen an operating system that
             | was not a toy that was able to reliably, gracefully handle
             | out of memory condition.
        
             | dekhn wrote:
             | So, uh, how do you feel about fractional reserve banking?
             | Nearly all banks worldwide practice it. Statistically, it's
             | not impossible that the entire world financial system could
             | collapse due to uncorrelated bank runs.
        
               | _3u10 wrote:
               | It is impossible. Only a moron of magnificent magnitude
               | would fail to print additional cash to cover the run.
               | 
               | The problems caused by the feds failure to lend to First
               | Bank of America during the Great Depression are well
               | understood by the central banks.
               | 
               | What would likely happen is the overnight rate would go
               | up to 12%, and additional money would be printed to cover
               | withdrawals for the month or two most people would be
               | willing to forgo 12% interest in a potentially
               | inflationary economy.
        
           | Spivak wrote:
           | That's exactly what fractional reserve banking is.
        
             | lmilcin wrote:
             | Funny thing, you are right. I was thinking about something
             | else I guess.
        
           | zeusk wrote:
           | What do you think is a bank run?
        
         | q-big wrote:
         | > I once complained about malloc happily allocating memory that
         | the physical memory system couldn't satisfy (never let your
         | hand write a check your ass can't cash?) but the more
         | experienced programmer asked me if I'd heard of fractional
         | reserve banking, and if not, whether it bothered me too.
         | 
         | What if you are worried about both? ;-)
        
       | andi999 wrote:
       | This has nothing to do with C. The problem/feature is with the
       | systemcall that does the allocation, and any language has to use
       | it.
        
         | baktubi wrote:
         | I pooped and scooped the poop said the Malloy to me be free.
         | Along came the scoop and scoop it went. Free it did. In the
         | bucket it tumbled for the others to enjoy.
        
         | jimbob45 wrote:
         | Is your issue that the system call doesn't return enough
         | diagnostic information? If so, how would you have done it
         | differently? I'm asking out of curiosity, not out of a
         | reflexive instinct to defend C (which has many problems).
        
           | joosters wrote:
           | The difficulty is that the lack of memory might be discovered
           | days after it was allocated (an over-committing allocator
           | simply doesn't know at the time whether there will be memory
           | or not) - how do you asynchronously tell the program that
           | this allocation has now failed in a high-level way?
           | 
           | Generally, UNIX will let you know about the missing memory by
           | sending the process a signal. But by that point, there's not
           | much that can be done to fix things up - and remember, all
           | the fix up code would have to run without allocating any more
           | memory itself. That's extremely tricky in C, and nigh-on
           | impossible in other languages.
        
       | not2b wrote:
       | On Linux, people should be pointed to
       | 
       | https://www.kernel.org/doc/Documentation/vm/overcommit-accou...
        
       | DougN7 wrote:
       | Is this really an issue with C? Isn't it ultimately an OS config
       | issue?
        
       | hdjjhhvvhga wrote:
       | This not "In C", this is a system-related issue. I always disable
       | overcommit as I see no benefit on my systems.
        
         | Arnavion wrote:
         | I used to disable overcommit, but I ran into a bunch of
         | compilers that would crash because of it despite having 30GiB
         | of free (as reported by free(1) ) memory available.
        
       | lmilcin wrote:
       | One of my interview questions starts with "Can a program allocate
       | more memory than is physically available on the server?"
       | 
       | Everybody gets this wrong (which is funny for a binary question)
       | but it starts an interesting discussion through which I hope to
       | learn how much they know about OS and virtual memory.
        
         | floxy wrote:
         | Don't leave us hanging. The "obvious" answer would seem to be
         | "yes" because of swap. But if everyone gets that wrong...
        
           | yjftsjthsd-h wrote:
           | No, it's worse than that - the answer is "yes", because
           | virtual memory + overcommit means that most of the time the
           | OS will happily allow you to allocate more memory than
           | physical+swap, and essentially gamble that you won't actually
           | need all of it (and this is implemented because apparently
           | that's almost always a winning bet).
        
             | lmilcin wrote:
             | Yeah. And the issue is that the actual problem happens
             | sometime later when the application actually tries to use
             | that memory. So you replaced an error that is relatively
             | simple to handle with something that is impossible to
             | handle reliably.
             | 
             | So the operating system very much doesn't like to admit it
             | doesn't have physical memory to back the area you are
             | trying to use. Now it does not have a simple way to signal
             | this to the application (there is no longer an option to
             | return an error code) and so either everything slows down
             | (as OS hopes that another process will return a little bit
             | of memory to get things going for a little while) or one of
             | the processes gets killed.
        
             | OldHand2018 wrote:
             | Processes come and go!
             | 
             | The OS doesn't have to gamble that you won't actually need
             | all the memory you allocate, it could just be a gamble that
             | another memory hogging process exits before you need to use
             | _all_ of your memory, or that you don 't need to use _all_
             | of your memory _at the same time_.
        
       | haxorrr666 wrote:
       | Nope. Works fine on Fedora 34:<BR> # free -h total used free
       | shared buff/cache available Mem: 31Gi 3.1Gi 2.2Gi 27Mi 25Gi 27Gi
       | Swap: 15Gi 62Mi 15Gi # uname -a Linux athena
       | 5.13.19-200.fc34.x86_64 #1 SMP Sat Sep 18 16:32:24 UTC 2021
       | x86_64 x86_64 x86_64 GNU/Linux # gcc -o memaloc memaloc.c #
       | ./memaloc error!
        
       | mirashii wrote:
       | I'm a little disappointed that the article didn't answer the
       | question, or at least try to. A discussion of using read/write vs
       | mincore vs trying to catch a SIGSEGV would've been a nice
       | addition.
        
         | kibwen wrote:
         | The answer is that you kind of can't. You're at the mercy of
         | the OS to give you accurate information, and malloc as an
         | interface isn't set up to distinguish between virtual memory
         | and "actual" memory. We could imagine a separate interface that
         | would allow the OS to communicate this distinction (or hacks
         | like you allude to), but I don't know of any standard approach.
        
           | convolvatron wrote:
           | before linux - every unix OS would fail a page allocation if
           | there wasn't a backing store. full stop.
           | 
           | this worked really well
        
       | davidw wrote:
       | This is why some embedded systems don't do malloc (IIRC...been a
       | while since I've read much about those).
        
         | edwinbalani wrote:
         | That's right - some "safe" coding standards like MISRA C go as
         | far as forbidding use of malloc() without explicit
         | justification.
         | 
         | If you still need dynamic allocation, you might choose to have
         | a custom allocator working from a fixed-size pool or arena
         | created in code (possibly itself carved out of RAM with
         | malloc(), but importantly only once at first setup, where you
         | have a better guarantee that the allocation will succeed).
        
           | edwinbalani wrote:
           | (All that said, embedded systems sometimes don't have virtual
           | memory, so the original problem stated in the link is just
           | not a thing...)
           | 
           | > working from a fixed-size pool or arena created in code
           | (possibly itself carved out of RAM with malloc(), but
           | importantly only once at first setup, where you have a better
           | guarantee that the allocation will succeed)
           | 
           | And I should add to this that you probably want to access all
           | of the pool/arena to do setup, or just ensure it's physically
           | allocated if you _are_ running in a virtual memory space.
           | This is something that is reasonable at setup time, though.
        
         | stefan_ wrote:
         | Embedded systems (the ones where you would disallow malloc)
         | don't generally have virtual memory by virtue of having no MMU,
         | so on those you can't do overcommitment since there is no page
         | fault mechanism.
         | 
         | No, the reason is simply that by statically allocating all
         | memory you can avoid entire classes of program faults and bugs.
         | There are no memory leaks and you don't need to solve the NP-
         | complete problem of "is there an execution path where a dynamic
         | memory allocation will fail". Keep in mind that it is not just
         | about the total amount of dynamically allocated memory, but
         | also the order of allocations (and frees).
        
         | tjoff wrote:
         | I wouldn't say this is the reason. Embedded systems typically
         | don't have virtual memory to start.
         | 
         | I would expect (but verify) a malloc implementation on embedded
         | to return null if it can't satisfy the allocation.
         | 
         | But even with that assumption malloc in embedded is often a bad
         | idea. You need to plan for worst case anyway and you can not
         | afford memory fragmentation.
        
       | haxorrr666 wrote:
       | Nope. Works fine on Fedora 34:
       | 
       | # free -h                              total        used
       | free      shared  buff/cache   available
       | 
       | Mem: 31Gi 3.1Gi 2.2Gi 27Mi 25Gi 27Gi
       | 
       | Swap: 15Gi 62Mi 15Gi
       | 
       | # uname -a
       | 
       | Linux athena 5.13.19-200.fc34.x86_64 #1 SMP Sat Sep 18 16:32:24
       | UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
       | 
       | # gcc -o memaloc memaloc.c
       | 
       | # ./memaloc
       | 
       | error!
        
       | yjftsjthsd-h wrote:
       | TLDR: "You don't." (Because malloc hands you virtual memory and
       | actually trying to use it might reveal that the system doesn't
       | have the _real_ memory to handle your request.
       | 
       | I kept reading hoping that there was going to be a solution, but
       | not really; there are comments discussing disabling overcommit,
       | but even that's a tradeoff (it _does_ fix this failure mode, but
       | you might not want to actually run a system like that).
        
         | one_off_comment wrote:
         | There's gotta be some way to programmatically determine it,
         | right? It may not be portable. It may require some system calls
         | or something, but there's gotta be a way, right?
        
           | wmf wrote:
           | I guess you could mlock() your memory and see if that
           | succeeds or fails. When overcommit is enabled no memory is
           | really safe.
        
           | yjftsjthsd-h wrote:
           | I'm not an expert enough to tell you; I just read the article
           | and decided that it was too long so summarized for others.
           | There's some discussion upthread about catching SIGSEGV and
           | other methods, FWIW.
        
       | itamarst wrote:
       | This difference between "malloc() succeeded and physical/swap
       | memory is actually available" also has somewhat corresponding
       | impact on how you measure memory usage.
       | 
       | One approach is RSS, the memory in physical RAM... but what if
       | you're swapping? Then again, maybe you swapped out memory you
       | don't actually need and ignoring swap is fine.
       | 
       | The other approach is "how much memory you allocated", and then
       | you hit fun issues mentioned in this article, like "the OS
       | doesn't actually _really_ allocate until you touch the page".
       | 
       | (Longer version: https://pythonspeed.com/articles/measuring-
       | memory-python/)
        
       | _3u10 wrote:
       | It's likely being OOM killed on Linux. Not sure what's happening
       | on Mac. Try allocating a terabyte of swap and it should run.
       | 
       | Alternatively use mmap & mlock to verify the allocation
       | succeeded, but the process can still be OOM killed at any time
       | for any reason.
        
         | valleyer wrote:
         | Similarly on macOS. There is a limit of 64 gigs in the VM
         | compressor (in-core and on-disk compressed "segments"
         | combined); when this is reached, a process that owns more than
         | 50% of the compressed memory can be killed.
         | 
         | See no_paging_space_action() in:
         | 
         | https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/ke...
         | 
         | Edit: I think the 64 gigs number is out of date -- looks like
         | it's now based in part on the amount of physical memory in the
         | machine.
        
           | _3u10 wrote:
           | You can tell the FreeBSD / xnu devs take their job more
           | seriously. A failure in the VM compressor sounds so much more
           | professional than being OOM killed.
        
       | pcwalton wrote:
       | It's important to remember the biggest reason why overcommit
       | exists on Linux and macOS: fork(). When a process forks, the vast
       | majority of the child process's memory is safely shared with the
       | parent process, due to copy-on-write. But a strict accounting
       | would say that the total memory usage of the system has doubled,
       | which is too conservative in most cases. Since forking is so
       | common on Unix, overcommit ends up being necessary pretty
       | quickly. Otherwise, fork/exec would stop working in any process
       | using more than half of the system memory.
        
         | jiveturkey wrote:
         | Most processes that fork know that they are going to fork.
         | Therefore they can pre-fork very early, so as to commit the
         | memory when they only have a bare minimum allocated. Your
         | typical daemon does this anyway.
         | 
         | Some other type of process like an interpreter that can
         | subshell out doesn't know how big the allocation is going to
         | get, would have to pre-fork early on.
         | 
         | In this way, you wouldn't "need" overcommit and the Linux
         | horror of OOM. Well, perhaps you don't need it so badly.
         | Programs that use sparse arrays without mmap() probably need
         | overcommit or lots of swap.
        
         | convolvatron wrote:
         | wouldn't you just account the COW pages against the parent
         | until they are copied?
         | 
         | kicking the can down the road means there isn't any longer a
         | reasonable correction (failing the allocation), but instead we
         | get to drive around randomly trying to find something to kill.
         | 
         | this is particularly annoying if you are running a service.
         | there is no hope for it to recover - for example by flushing a
         | cache. instead the OS looks around - sees this fat process just
         | sitting there, and .. good news, we have plenty of memory now.
        
           | pjc50 wrote:
           | But what happens when the kernel needs to copy one and has
           | run out of memory? You still get a random process killed.
           | 
           | (I note that Windows has a different approach, with "reserve"
           | vs "commit", but nobody regards that as a preferential reason
           | for using Windows as a server OS)
        
           | trhway wrote:
           | >there is no hope for it to recover - for example by flushing
           | a cache. instead the OS looks around - sees this fat process
           | just sitting there, and .. good news, we have plenty of
           | memory now.
           | 
           | Overcommit was godsent in the times of expensive memory and
           | when people used virtual memory on disk (so it will spill low
           | use memory pages there instead of the kill). Of course these
           | days with abundance of cheap memory and people not
           | configuring virtual memory any more we get the situation you
           | describe.
        
           | secondcoming wrote:
           | Indeed. It's totally dumb that the OS is allowed lie to you
           | when you've asked for some resources. And because of this
           | behaviour people no longer check for success from malloc(),
           | etc, because of laziness. It's a bad situation.
        
             | joosters wrote:
             | Please don't blindly declare it 'totally dumb'. If you
             | disallow overcommit, you can end up with a system that
             | can't fork and run /bin/true, even if there are gigabytes
             | of memory left.
             | 
             | Both styles of memory allocation have their uses, and their
             | drawbacks, but please understand them before declaring many
             | OS designers as stupid and dumb.
        
               | giomasce wrote:
               | I suppose vfork might help with that (though I don't
               | understand why they don't directly add fork_and_execve,
               | it would seem much easier).
               | 
               | Also, the problem with Linux is not having overcommit,
               | but notv being able to choose when to overcommit and when
               | not. Windows makes that easier, AFAIU.
        
               | gpderetta wrote:
               | They do. Man posix_spawn. It comes with its own dsl to be
               | able to support a small subset of all operations that are
               | often performed between fork and execv.
               | 
               | Vfork is usually a better solution.
               | 
               | Edit: and yes, I would love to be able to disable
               | overcommit per process.
        
               | OldHand2018 wrote:
               | This paper [1] argues that fork needs to be deprecated in
               | favor of posix_spawn() or other more modern solutions. It
               | claims that, among many other reasons, that fork
               | encourages overcommit and that programs such as Redis are
               | extraordinarily constrained if overcommit is disabled -
               | because of fork.
               | 
               | [1] https://dl.acm.org/doi/10.1145/3317550.3321435
        
               | joosters wrote:
               | It's a weakness of the fork()+exec() model, for sure.
               | However, creating a fork_and_execve() API is extremely
               | tricky. Just think of all the innumerable setup options
               | you would need to give it, e.g. what file handles should
               | be closed or left open? What directory should it start
               | in? What environment variables should be set - or
               | cleared? And on and on and on...
               | 
               | the flexibility of a separate fork() then exec() means
               | you can set up the initial state of a new process exactly
               | as you want, by doing whatever work is needed between the
               | two calls. If you merge them into one, then you will
               | never be able to encapsulate all of that.
        
             | OldHand2018 wrote:
             | Let's say you malloc some memory and the computer actually
             | has everything available.
             | 
             | Everything is great, up until some other process on your
             | system does a fork bomb of an infinitely recursive program
             | that allocates nothing on the heap. You've just got a whole
             | lot of quickly growing stacks hoovering up your physical
             | memory pages.
        
             | alexgartrell wrote:
             | Allocated-but-unavailable is a totally reasonable part of
             | the memory hierarchy.
             | 
             | Main Memory => zswap (compressed memory) => swap
             | 
             | In this case, the pages may be logically allocated or not
             | -- the assurance is that the data will be the value you
             | expect it to be when it becomes resident.
             | 
             | Should those pages be uninitialized, the "Swapped" state is
             | really just "Remember that this thing was all zeros."
             | 
             | We could do computing your way, but it'd be phenomenally
             | more expensive. I know this because every thing we
             | introduce to the hierarchy in practice makes computing
             | phenomenally _less_ expensive.
        
               | cturner wrote:
               | "We could do computing your way, but it'd be phenomenally
               | more expensive"
               | 
               | It must be viable - Windows prevents overcommit. But it
               | has slow child-process-creation (edit: previously said
               | "forking"), and this steers development towards native
               | threads which is its own set of problems.
               | 
               | I had never previously joined the dots on the point
               | pcwalton makes at the top of this thread. It is a
               | dramatic trade-off.
        
               | pjc50 wrote:
               | Windows does not have fork(), apart from the hidden
               | ZwCreateProcess;
               | https://news.ycombinator.com/item?id=9653975
               | 
               | For ages cygwin had to emulate it manually by hand-
               | copying the process.
        
           | pcwalton wrote:
           | > wouldn't you just account the COW pages against the parent
           | until they are copied?
           | 
           | That's what Linux does. But how do you return ENOMEM when the
           | copy does happen and now the system is out of memory? Memory
           | writes don't return error codes. The best you could do is
           | send a signal, which is exactly what the OOM killer does.
        
         | wahern wrote:
         | > Otherwise, fork/exec would stop working in any process using
         | more than half of the system memory.
         | 
         | Somehow Solaris manages just fine.
         | 
         | And don't forget that swap memory exists. Ironically, using
         | overcommit without swap is asking for trouble on Linux.
         | Overcommit or no overcommit, the Linux VM and page buffer
         | systems are designed with the expectation of swap.
        
           | joosters wrote:
           | Solaris does have this problem! If you have a huge program
           | running, fork()img it can fail on Solaris, even if you only
           | want to just exec a tiny program. The key way of avoiding
           | this is to ensure you have lots and lots of swap space.
        
           | pjc50 wrote:
           | How _does_ Solaris handle this? Or Darwin?
        
             | wahern wrote:
             | Solaris has strict memory accounting; fork will fail if the
             | system can't guarantee space for all non-shared anonymous
             | memory. macOS has overcommit (all BSDs do to some extent,
             | at least for fork), but it also automatically creates swap
             | space so you rarely encounter issues one way or another in
             | practice.
             | 
             | fork and malloc can also fail in Linux even with overcommit
             | enabled (rlimits, but also OOM killer racing with I/O page
             | dirtying triggering best-effort timeout), so Linux buys you
             | a little convenience at the cost of making it impossibly
             | difficult to actually guarantee behavior when it matters
             | most.
        
           | _3u10 wrote:
           | Even without overcommit swap makes the system work better as
           | unused pages can be written to disk and that memory used for
           | disk cache and I think can also help with defragmentation of
           | memory not sure how that process actually works.
        
         | dekhn wrote:
         | The biggest reason overcommit exists is because it allows the
         | system to operate more efficiently. The reality is most
         | applications touch only some of the pages they allocate, and
         | it's silly for the system to fail a malloc. Often times other
         | expensive cleanup activities can be deferred (you don't really
         | want to drop a handy directory entry cache just so an app can
         | be sure it got physically backed memory for its request, 99.99%
         | of the time).
         | 
         | IIUC Linux was really the first OS to make overcommit so
         | prominent. Most systems were a lot more conservative.
        
           | joosters wrote:
           | In theory, a system without overcommit can run just as
           | efficiently, IF you have reserved huge amounts of swap space.
           | As long as swap is available, the OS can do all the same COW
           | and efficiency tricks. It's not the physically backed memory
           | is the limiting factor, it's RAM+swap
        
             | dekhn wrote:
             | no, the kernel maintains other allocated objects (dentry,
             | inode caches) that it can't swap out. Under memory
             | pressure, those get dropped before application pages. See
             | https://unix.stackexchange.com/questions/17936/setting-
             | proc-... and
             | https://unix.stackexchange.com/questions/111893/how-long-
             | do-...
             | 
             | I've found the linux memory system to be far too
             | complicated to understand for quite some time, compared to
             | what's documented in, for example, The Design and
             | Implementation of The FreeBSD Operating System, for a more
             | comprehensible system.
        
               | joosters wrote:
               | Those objects must exist with and without overcommit, I
               | don't understand why they must make one less efficient
               | than the other.
               | 
               | (I'm talking in general here - not Linux specifically)
        
               | dekhn wrote:
               | dentry caches exist with and without overcommit, but you
               | get higher cache hit rates with overcommit, because you
               | flush them less recently. Depending on workload, this can
               | matter a lot. It mattered more in the time of hard
               | drives.
        
               | joosters wrote:
               | I'm sorry, but I'm still not following... why must a
               | cache be (in theory) flushed more often without
               | overcommit?
        
               | dekhn wrote:
               | if overcommit is disabled, then the system drops its
               | internal caches (dentry cache, pagecache) to satisfy a
               | memory allocation. Since most applications don't touch
               | pages they allocate, that means the OS could have avoided
               | dropping the caches. Since it did drop the caches, other
               | parts of the system will then have to do more work to
               | reconstruct the caches (looking up an dentry explicitly,
               | or loading a page from disk instead of RAM).
               | 
               | Everything I'm describing is about a busy server with
               | heterogenous workloads of specific types.
        
         | secondcoming wrote:
         | Even the linux docs for it suggest to turn it on if you're
         | working with sparse arrays, there's no mention of fork()
         | 
         | https://www.kernel.org/doc/Documentation/vm/overcommit-accou...
        
       ___________________________________________________________________
       (page generated 2021-11-05 23:00 UTC)