C stdlib isn't threadsafe and even safe Rust didn't save us
Non-thread-safe environment APIs in C/POSIX
- Core issue:
setenv/unsetenv(andputenv,environ) are not required to be reentrant or thread‑safe by POSIX; many implementations aren’t. getenvis also tricky: the returned pointer may be invalidated by later env calls; POSIX/ISO C give weak guarantees.- Exposing
extern char **environmakes it impossible to fully “fix” things without breaking ABI: code can mutate the environment behind libc’s back.
Impact on Rust and other languages
- Rust’s
std::env::set_varoriginally wrappedsetenvas a safe function; it’s now documented as unsafe on most non‑Windows platforms and will requireunsafein Rust 2024. - Rust can’t synchronize with C libraries that call
getenv/setenvdirectly, so a Rust-only mutex cannot fix the problem. - Even a Rust‑native libc or alternative runtime doesn’t help if C/FFI code still uses the platform libc and its environment.
Can libc or POSIX fix this?
- One camp: libc should make env operations safe by default (e.g., mutexes, copy‑on‑write, per‑thread views, never freeing old strings, Solaris/illumos-style fixes, glibc’s recent “leak instead of crash” change).
- Counterarguments:
- The API shape (shared pointers,
environ) fundamentally prevents full MT safety. - Retrofitting safety risks breaking existing programs or causing unbounded leaks.
- Standards explicitly say these calls need not be thread‑safe; changing that is hard.
- The API shape (shared pointers,
Environment variables as mutable global state
- Strong sentiment that env vars should be read-only after startup, like argv.
- Many argue
setenv“shouldn’t have existed” or should be reserved for building child-process environments, not intra-process configuration. - Others note legitimate cases (e.g., dlopen’ing libraries configured via env, allocators/sanitizers, shells) but agree mutation after threads start is dangerous.
Workarounds and safer patterns
- Common recommendations:
- Read env exactly once at startup, copy into your own data structure, then treat as immutable.
- Avoid libraries that mutate env at runtime; prefer APIs that accept explicit config instead of reading env.
- In extreme cases, override
getenv/setenvviaLD_PRELOADor use alternative libcs that leak instead of corrupting.
- Some suggest new, explicitly thread-safe env APIs (buffer‑based or copy‑returning), but migration and coexistence with
environremain unsolved.
Broader reflections
- Debate over “safe by default” vs performance and historical constraints.
- Comparisons to Windows’ and JVM’s environment handling, which are effectively thread-safe or immutable, seen as vindicating stricter designs.