Fix data race in mju_getLogConfigPtr lazy init
The lazy init of log_config used a single atomic flag (env_checked) with a load-then-store pattern, allowing two threads to both enter the init path and concurrently write to the non-atomic log_config.topics field. Replace with a two-phase atomic init: an atomic exchange on env_init_claimed ensures exactly one thread enters the init, while env_init_done (with acquire/release semantics) signals completion and provides the happens-before edge that makes log_config writes visible to other threads. Also adds mj_atomic_exchange_bool to engine_crossplatform.h (both MSVC and GCC/Clang variants). PiperOrigin-RevId: 941202181 Change-Id: I59b6801c7b4b8b0e1647d82d2aeb534a90fb66fb
This commit is contained in:
committed by
Copybara-Service
parent
6e8a79c657
commit
bb80b55ae1
@@ -85,6 +85,20 @@
|
||||
__atomic_fetch_add(ptr, val, __ATOMIC_RELAXED)
|
||||
#endif
|
||||
|
||||
// Atomics helpers for mjtBool (1-byte _Bool) with acquire/release semantics.
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define mj_atomic_load_bool(ptr) \
|
||||
(mjtBool) _InterlockedCompareExchange8((volatile char*)(ptr), 0, 0)
|
||||
#define mj_atomic_store_bool(ptr, val) \
|
||||
(void)_InterlockedExchange8((volatile char*)(ptr), (char)(val))
|
||||
#define mj_atomic_exchange_bool(ptr, val) \
|
||||
(mjtBool) _InterlockedExchange8((volatile char*)(ptr), (char)(val))
|
||||
#else
|
||||
#define mj_atomic_load_bool(ptr) __atomic_load_n(ptr, __ATOMIC_ACQUIRE)
|
||||
#define mj_atomic_store_bool(ptr, val) __atomic_store_n(ptr, val, __ATOMIC_RELEASE)
|
||||
#define mj_atomic_exchange_bool(ptr, val) __atomic_exchange_n(ptr, val, __ATOMIC_ACQ_REL)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -104,7 +104,8 @@ static mjLogConfig log_config = {.logto_console = true,
|
||||
.logto_file = true,
|
||||
.logfile = "MUJOCO_LOG.TXT",
|
||||
.topics = 0};
|
||||
static mjtBool env_checked = 0;
|
||||
static mjtBool env_init_claimed = 0; // claimed by first thread to enter init
|
||||
static mjtBool env_init_done = 0; // set after log_config init completes
|
||||
|
||||
// parse MUJOCO_LOG_TOPICS env var to seed initial topic bitmask
|
||||
// example: MUJOCO_LOG_TOPICS="time_stp,sleep"
|
||||
@@ -143,9 +144,17 @@ static void mju_initLogTopicsFromEnv(void) {
|
||||
|
||||
// private pointer getter encapsulates lazy init with zero copy overhead
|
||||
static const mjLogConfig* mju_getLogConfigPtr(void) {
|
||||
if (!env_checked) {
|
||||
mju_initLogTopicsFromEnv();
|
||||
env_checked = 1;
|
||||
if (!mj_atomic_load_bool(&env_init_done)) {
|
||||
// atomically claim right to initialize; only one thread gets old value 0
|
||||
if (!mj_atomic_exchange_bool(&env_init_claimed, 1)) {
|
||||
mju_initLogTopicsFromEnv();
|
||||
mj_atomic_store_bool(&env_init_done, 1);
|
||||
} else {
|
||||
// another thread is initializing; spin until it completes
|
||||
while (!mj_atomic_load_bool(&env_init_done)) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
}
|
||||
return &log_config;
|
||||
}
|
||||
@@ -171,8 +180,9 @@ mjLogConfig mju_getLogConfig(void) {
|
||||
|
||||
// set default handler configuration
|
||||
void mju_setLogConfig(mjLogConfig config) {
|
||||
env_checked = 1;
|
||||
log_config = config;
|
||||
mj_atomic_store_bool(&env_init_claimed, 1);
|
||||
mj_atomic_store_bool(&env_init_done, 1);
|
||||
}
|
||||
|
||||
// restore default processing
|
||||
@@ -182,8 +192,9 @@ void mju_clearHandlers(void) {
|
||||
.logto_file = true,
|
||||
.logfile = "MUJOCO_LOG.TXT",
|
||||
.topics = 0};
|
||||
env_checked = 1;
|
||||
mj_atomic_store_bool(&env_init_claimed, 1);
|
||||
mju_initLogTopicsFromEnv();
|
||||
mj_atomic_store_bool(&env_init_done, 1);
|
||||
|
||||
mju_user_error = 0;
|
||||
mju_user_warning = 0;
|
||||
|
||||
Reference in New Issue
Block a user