I've found some time to try it out... and after all, I've no idea where the problem is, that set_var should be marked unsafe.
This threading code, calling std::env::var and std::env::set_var the whole time, runs forever. Tried with gnu and musl libc. There is no error, no crash and the data is correct, of course you can't be sure which value is set when and if the getter-thread runs before or after a setter-thread, because you have no control when the thread is scheduled. But same problem exists during file writing:
use std::{
io::{self, Write as _},
thread,
time::Duration,
};
use nanorand::Rng;
const ENV_VARNAME: &str = "ENV_TEST_VAR";
const ERROR_WAIT_US: Duration = Duration::from_micros(10);
const THREAD_COUNT: usize = 20;
const THREAD_SLEEP_MS: Duration = Duration::from_millis(1000);
fn rnd_value(len: u8, rng: &mut nanorand::tls::TlsWyRand) -> String {
let charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let mut value = format!("{}", len + 1);
for _ in 0..len {
let index = rng.generate_range(0..charset.len());
value.push_str(&charset[index..=index]);
}
value
}
fn main() {
let env_value = std::env::var(ENV_VARNAME);
assert!(env_value.is_err());
const JH: Option<thread::JoinHandle<()>> = None;
let mut th_getenv = [JH; THREAD_COUNT];
for thread in &mut th_getenv {
*thread = Some(thread::spawn(|| loop {
let env_value = std::env::var(ENV_VARNAME);
match env_value {
Ok(env_value) => {
let _ = writeln!(io::stdout(), "env::var: {ENV_VARNAME}: {env_value}");
let c = env_value.chars().next().expect("no char");
assert!(env_value.len() as u32 == c.to_digit(10).expect("no digit"));
}
Err(err) => {
let _ = writeln!(io::stderr(), "env::var fail: {err:?}");
thread::sleep(ERROR_WAIT_US);
}
}
}));
}
let mut th_setenv = [JH; THREAD_COUNT];
for thread in &mut th_setenv {
*thread = Some(thread::spawn(|| {
let mut rng = nanorand::tls_rng();
loop {
let value_len = rng.generate_range(1u8..9);
let value = rnd_value(value_len, &mut rng);
// edition > 2021 - Why???
// unsafe { std::env::set_var(ENV_VARNAME, value); }
// edition <= 2021
std::env::set_var(ENV_VARNAME, &value);
// no error handling possible
let _ = writeln!(io::stdout(), "env::set_var: {ENV_VARNAME}: {value}");
}
}));
}
loop {
thread::sleep(THREAD_SLEEP_MS);
}
}
More or less the same works also in C++23. Here stdout/err is not locked by default, like in Rust...
#include <algorithm>
#include <array>
#include <cassert>
#include <cerrno>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <string>
#include <thread>
namespace {
constexpr const char *k_env_varname = "ENV_TEST_VAR";
constexpr const int64_t k_error_wait_us = 10;
constexpr const std::size_t k_thread_count = 20;
constexpr const int64_t k_thread_sleep_ms = 1000;
} // namespace
/// @brief Generate random `std::string` with length digit at start
///
/// @param len Length of `std::string` [1..=9]
///
/// @return random `std::string`
auto rnd_value(uint8_t len) -> std::string {
auto randchar = []() -> char {
const auto charset = std::to_array(
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
const auto max_index = charset.size() - 1;
const auto index =
static_cast<unsigned long>(rand()) % max_index; // NOLINT
return charset.at(index);
};
std::string value(len + 1, 0);
std::generate_n(value.begin() + 1, len, randchar);
value[0] = static_cast<char>('1' + len);
return value;
}
auto main() -> int {
// Flawfinder: ignore
const auto *env_value = std::getenv(k_env_varname); // NOLINT
assert(!env_value);
auto th_getenv = std::array<std::jthread, k_thread_count>{};
for (auto &thread : th_getenv) {
thread = std::jthread{[] {
for (;;) {
// Flawfinder: ignore
const auto *env_value = std::getenv(k_env_varname); // NOLINT
if (env_value == nullptr) {
// NOLINTNEXTLINE
std::cerr << "getenv fail: " << std::strerror(errno)
<< "\n";
std::this_thread::sleep_for(
std::chrono::microseconds{k_error_wait_us});
} else {
std::cout << "getenv: " << k_env_varname << ": "
<< env_value << "\n";
const auto value = std::string{env_value};
// Flawfinder: ignore
assert(value.length() ==
static_cast<std::size_t>(value[0] - '0'));
}
}
}};
}
auto th_setenv = std::array<std::jthread, k_thread_count>{};
for (auto &thread : th_setenv) {
thread = std::jthread{[] {
for (;;) {
// NOLINTNEXTLINE(cert-msc30-c,cert-msc50-cpp,concurrency-mt-unsafe)
const uint8_t value_len = 1 + static_cast<uint8_t>(rand() % 9);
const auto value = rnd_value(value_len);
// Flawfinder: ignore
auto ret = setenv(k_env_varname, value.data(), 1); // NOLINT
if (ret != 0) {
// NOLINTNEXTLINE
std::cerr << "setenv fail: " << std::strerror(errno)
<< "\n";
std::this_thread::sleep_for(
std::chrono::microseconds{k_error_wait_us});
} else {
std::cout << "setenv: " << k_env_varname << ": " << value
<< "\n";
}
}
}};
}
for (;;) {
std::this_thread::sleep_for(
std::chrono::milliseconds{k_thread_sleep_ms});
}
return 0;
}