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:
Georg Ostrovski
2026-07-01 11:23:55 -07:00
committed by Copybara-Service
parent 6e8a79c657
commit bb80b55ae1
2 changed files with 31 additions and 6 deletions
+14
View File
@@ -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