How pgrust Manages Dynamic Library Loading for PostgreSQL Extensions
pgrust implements PostgreSQL’s dynamic library loader entirely in Rust using two distinct pathways: an OS‑edge loader that gracefully fails for unported C extensions, and an in‑process registry that resolves symbols for libraries ported to Rust, eliminating the need for dlopen while maintaining PostgreSQL‑compatible error handling.
pgrust is a Rust implementation of the PostgreSQL backend that replicates the server’s extension loading mechanism without exposing a C ABI. Unlike traditional PostgreSQL backends that rely on dlopen and dlsym to load .so files, pgrust handles dynamic library loading for PostgreSQL extensions through a hybrid seam architecture that keeps the runtime entirely in safe Rust.
The OS-Edge Loader: Handling Unported Libraries
When pgrust encounters a request for a library that has not been ported from C to Rust, it delegates to the dynloader crate. This crate provides OS‑edge seams that wrap POSIX system calls but deliberately prevent actual dynamic loading to preserve the no‑C‑ABI invariant.
Filesystem Detection with stat_identity
The loader performs real filesystem checks to determine if a library file exists and identify its inode. In crates/port/dynloader/src/lib.rs (lines 40‑79), the stat_identity seam wraps the POSIX stat(2) syscall to support “same‑inode” detection, allowing pgrust to verify file identity without loading the library.
Graceful Failure for Missing C Extensions
All dynamic loading operations in the OS‑edge pathway are designed to fail gracefully. The open_library seam in crates/port/dynloader/src/lib.rs (lines 82‑107) aborts with a PostgreSQL‑style ereport(ERROR) message before attempting any actual dlopen operation. Other seams—including call_pg_init, function_exists, fetch_finfo_record, plugin_init, and invoke_output_plugin_callback—are stubbed to return deterministic errors via no_c_abi_error and panic only if reached, which should never occur because open_library terminates execution first.
This behavior mirrors PostgreSQL’s real failure mode for missing libraries while keeping the Rust backend alive rather than causing an uncontrolled panic.
The In-Process Registry: Rust-Native Extensions
Extensions whose C bodies have been ported to Rust—such as plpgsql, test_decoding, and regress—bypass the OS loader entirely by registering themselves in a shared registry maintained by the dfmgr_seams crate.
Registering Ported Libraries
Each ported crate calls register_builtin_library from its init_seams() function to insert a BuiltinLibraryEntry into the registry. This entry contains the library name, a lookup function for resolving symbols, and an optional _PG_init‑equivalent initialization routine.
// Example: Register a ported library (e.g. plpgsql) in its init_seams()
use pgrust::dfmgr_seams::register_builtin_library;
use pgrust::dfmgr_seams::BuiltinLibraryEntry;
use pgrust::fmgr::LoadedExternalFunc;
fn plpgsql_lookup(symbol: &str) -> Option<LoadedExternalFunc> {
// map “function_name” → LoadedExternalFunc
// (implementation omitted for brevity)
unimplemented!()
}
pub fn init_seams() {
register_builtin_library(BuiltinLibraryEntry {
name: "plpgsql",
lookup: plpgsql_lookup,
pg_init: Some(plpgsql_pg_init), // optional init routine
});
}
Symbol Resolution via the Registry
When the backend needs to load a function, the dfmgr_seams crate queries the registry before falling back to the OS‑edge loader. In crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs (lines 24‑31, 38‑45, 61‑71), the functions builtin_library_present and resolve_builtin_library_function check whether a requested library is already known. If found, the loader returns the Rust implementation directly without invoking dlopen.
// Example: Loading a function – the backend first checks the registry
fn load_external_function(probin: &str, prosrc: &str, oid: Oid) -> PgResult<LoadedExternalFunc> {
// Try the builtin registry first
if let Some(func) = pgrust::dfmgr_seams::resolve_builtin_library_function(probin, prosrc)? {
return Ok(func);
}
// Fallback to the OS‑edge loader (will always error in the Rust build)
pgrust::dynloader_seams::open_library::call(probin)?;
// unreachable – the above always returns an error
}
Integration with the Function Manager
The dfmgr crate (crates/backend/utils/fmgr/dfmgr/src/lib.rs) serves as the integration layer that delegates to either the OS‑edge loader or the builtin registry. This architecture allows pgrust to maintain API compatibility with PostgreSQL’s dynamic library loading interface while internally routing requests through safe Rust code paths.
Summary
- OS‑edge loader (
dynloadercrate): Wrapsstat(2)for filesystem checks but forces graceful errors for any attempt todlopenunported C libraries. - In‑process registry (
dfmgr_seamscrate): StoresBuiltinLibraryEntrystructs for ported extensions, enabling direct symbol resolution without dynamic loading. - Graceful degradation: Unported extensions trigger PostgreSQL‑style error reports rather than crashes, maintaining stability.
- Safe Rust implementation: All loading logic avoids
unsafeblocks and C FFI, relying instead on Rust function pointers and registry lookups.
Frequently Asked Questions
How does pgrust handle extensions that haven’t been ported to Rust?
When pgrust encounters an unported extension, the open_library seam in crates/port/dynloader/src/lib.rs aborts with an ereport(ERROR) message before attempting any system dlopen call. This mirrors PostgreSQL’s error behavior for missing libraries while preventing the Rust backend from attempting to load C ABI code.
Can pgrust load actual .so files at runtime?
No. The Rust backend does not expose a C ABI, so open_library always fails gracefully with a descriptive error. Actual dynamic library loading is only possible for extensions that have been fully ported to Rust and registered via register_builtin_library.
Where does the registry store ported library information?
The registry is maintained in the dfmgr_seams crate (crates/backend/utils/fmgr/dfmgr_seams/src/lib.rs). Ported crates like plpgsql and test_decoding call register_builtin_library during their initialization to insert BuiltinLibraryEntry structs containing symbol lookup functions and optional initialization callbacks.
What happens if stat_identity finds the library file but it isn’t in the registry?
The stat_identity seam confirms the file exists on disk, but the loading process still proceeds through open_library, which will return an error because the library is not registered as a builtin. This two‑phase check ensures PostgreSQL‑compatible error messages while maintaining the safety invariant that no C code is executed.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →