#Can you handle an error when the system runs out of memory?

1 messages · Page 1 of 1 (latest)

scenic tundra
#

I am new to systems programming and just trying to better understand what is happening. I made this little experiment to better understand what happens when a zig allocator runs out of memory, but I don't really understand why one crashes and the other does not. does anyone have advice for how to handle system out of memory without the program crashing, or an explanation on why that is not possible to do?
I am developing on pop-os 22.04 and using zig version 0.11.0-dev.1457+cb9d00e1a.

// this fails with exit code 1
pub fn main() !void {
    var arena_allocator = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    var allocator = arena_allocator.allocator();

    var is_memory_available = true;
    while (is_memory_available) {
        _ = allocator.alloc(u8, 1024) catch |err| {
            std.debug.print("error: {}\n", .{err});
            is_memory_available = false;
            arena_allocator.deinit();
        };
    }
    std.debug.print("done\n", .{});
}
// this works fine, it prints out there error and then prints "done"
pub fn main() !void {
    var heap_buffer: [4096]u8 = undefined;
    var fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(&heap_buffer);
    var fixed_allocator = fixed_buffer_allocator.allocator();
    var arena_allocator = std.heap.ArenaAllocator.init(fixed_allocator);
    var allocator = arena_allocator.allocator();

    var is_memory_available = true;
    while (is_memory_available) {
        _ = allocator.alloc(u8, 1024) catch |err| {
            std.debug.print("error: {}\n", .{err});
            is_memory_available = false;
            arena_allocator.deinit();
        };
    }
    std.debug.print("done\n", .{});
}
full scarab
#

well, #2 will show an error as soon as it allocates 4096 bytes, since that's how big the backing buffer is

#

i'd say var heap_buffer is a misnomer; that program does not use any heap memory

#

so #1 is the only one that actually runs out of memory at the OS level

#

as for why #1 behaves the way it does, i would guess it has to do with linux's optimistic approach to allocating memory

#

afaik, when you ask the kernel to give your process memory (which in your case happens every time that the ArenaAllocator asks the page_allocator for more memory), it will basically give you a pointer to some virtual address space right away, but it won't yet be backed by actual physical memory

#

which means that reads on that memory region return zero and writes trigger a page fault, which wakes up the kernel. and the page fault is when it tries to actually allocate memory to your process.

#

the other behavior in play is that newly allocated memory in zig is set to undefined, so if you built that in debug mode then page_allocator will fill any new memory it gets with 0xAA

#

so my guess as to what happened is that once you were out of memory, page_allocator was still able to get """memory""" allocated from the OS, except when it tried to write to the memory it triggered a segmentation fault because linux couldn't find any actual memory to give to your process

scenic tundra
#

Thanks! That all makes a lot of sense, but kind opens up a few more questions

#

is var heap_buffer on the stack and is it bad to have a huge amount of data on the stack like that (say 10gigs)?

#

and if page_allocator did not write 0xAA to the memory would it not segfault and instead allow the catch |err| {...} to run?

storm reef
#

allocating 10g on the stack would immediately kill your program

storm reef
#

If you disable overcommit you'll get an error returned from mmap (which is what page_allocator calls internally) and your catch will be run

#

overcommit is a system-wide kernel setting though, and it's enabled by default on most Linux systems so your code should assume it's on unless you're targeting a system you have complete control over

full scarab
storm reef
#

Sure, you could increase the stack size limit, but you probably shouldn't

full scarab
#

yeah lol

storm reef
#

If you're getting stack overflows, it's a good sign you're doing something wrong

full scarab
#

and if you write past the end of the stack you crash since you end up in unmapped memory

storm reef
#

In this case, you should just allocated your "heap buffer" with page_allocator on startup

storm reef
#

But depending on the access pattern you could end up going past that with a 10G buffer

full scarab
#

is that to make sure that stack overflows don't accidentally end up in another mapped region? i guess since this all happens at startup maybe the stack could end up next to some static data or code or something

storm reef
storm reef
full scarab
#

ooh, i didn't know that was possible

storm reef
#

It wouldn't normally end up next to code, but it could hit the heap

#

(stack is at the top of the address space, code sections are normally near the bottom)

storm reef
#

Similarly to how swap, overcommit, and memory mapped files work

scenic tundra
storm reef
#

Yep, exactly :)

#

When the system gets low on RAM, instead of telling applications "no we don't have any memory left" it instead says "sure, you can allocate memory" and then kills them when they try to use it