Conversation
…locator On rustc >= 1.93, the second out-of-memory panic that unwinds out of the global allocator in one backend aborts the whole backend with 'fatal runtime error: failed to initiate panic' (bisected: 1.92 unwinds correctly, 1.93 aborts). An unwind out of GlobalAlloc is documented undefined behavior, -Zoom=panic was removed in Rust 1.94, and set_alloc_error_hook is still nightly-only, so no stable panic-based design exists. On the backend main thread (recorded in _PG_init), report OOM the way PostgreSQL C code does: errstart/errfinish longjmp to the error handler and abort the transaction without unwinding Rust frames. Skipped frames leak their allocations on this path, which is acceptable. Other threads keep the panic fallback. The err* symbols are declared locally because pgrx does not expose them in pg_sys.
6a7c49f to
fe96672
Compare
| type Errstart = unsafe extern "C" fn(::core::ffi::c_int, *const ::core::ffi::c_char) -> bool; | ||
| type Errcode = unsafe extern "C" fn(::core::ffi::c_int) -> ::core::ffi::c_int; | ||
| type Errmsg = unsafe extern "C" fn(*const ::core::ffi::c_char, ...) -> ::core::ffi::c_int; | ||
| type Errfinish = unsafe extern "C" fn( |
There was a problem hiding this comment.
Wouldn't it make more sense for these to be C-unwind instead?
| /// it is safe to call from inside the global allocator. | ||
| unsafe fn pg_oom_error() -> ! { | ||
| unsafe { | ||
| let errstart = dlsym(RTLD_DEFAULT, c"errstart".as_ptr()); |
There was a problem hiding this comment.
Looks like dlsym does not allocate memory under normal fast paths, but digging out implementations we can see that glib for example can reach malloc/calloc calls for unique symbols, https://raw.githubusercontent.com/bminor/glibc/master/elf/dl-lookup.c in this particular malloc call it seems safe that it was not able to allocate.
This alone makes me wonder whether trusting the allocation-less contract with dlsym across versions/implementations/platforms is correct, what would happen if an implementation calls the same allocator as Rusts's and cannot allocate, can it recurse back to our own handler? I see this as a very edgy edgy case but something similar happened many years ago in RealmDB in some specific Android/Platform version and it took them weeks to debug, I'd like to avoid this.
can we alternatively load the symbols in _PG_init and have them cached? This seems perfectly safe as an allocation there would never fail.
Problem
palloc.rsturns a failed allocation into a Rustpanic!from insideGlobalAlloc. On rustc 1.93 and later, this design aborts the whole backend: the first OOM panic unwinds and becomes a clean transaction abort, but the second one in the same backend fails withfatal runtime error: failed to initiate panic, and PostgreSQL restarts. A toolchain bisect shows 1.92 unwinds correctly and 1.93 aborts (the suspected trigger is the allocator/TLS rework in rust-lang/rust#144465). This repo pins rust 1.96.0 (tools/dependencies.sh), so the abort is reachable today.There is no stable panic-based replacement: an unwind out of
GlobalAllocis documented undefined behavior,-Zoom=panicwas removed in Rust 1.94, andset_alloc_error_hook(rust-lang/rust#51245) is still nightly-only.Change
On the backend main thread (recorded in
_PG_init), the allocator now reports OOM the way PostgreSQL C code does. It callserrstart/errcode/errmsg/errfinish, which longjmp to the active PostgreSQL error handler and abort the transaction. No Rust frames unwind, so the rustc limitation does not apply. PostgreSQL keeps a preallocated reserve in its ErrorContext, so the report also works under genuine memory exhaustion.Trade-offs and edges:
panic!fallback.err*symbols are declared locally because pgrx does not expose them inpg_sys(pgrx routes errors through its panic-basedereport!macro, which is exactly the mechanism that cannot be used inside the allocator).