resource_manager: fix data buffer teardown racing async load job tail

ma_resource_manager_data_buffer_uninit takes a synchronous teardown path as
soon as it observes result == MA_SUCCESS, freeing the buffer. But the load
job (ma_job_process__resource_manager__load_data_buffer) publishes that
result code via a CAS several instructions before it finishes referencing
the buffer: it still reads has_connector() and does the executionPointer
increment afterward. A concurrent uninit that observes MA_SUCCESS therefore
frees the buffer out from under the job tail, which then reads and
atomically increments freed (and possibly recycled) memory.

The asynchronous free path is safe because it waits on the order /
executionPointer ticket scheme; the synchronous shortcut bypassed it. Make
the synchronous path wait for executionPointer to catch up to
executionCounter before tearing down, so no job is still referencing the
buffer. Caught by ThreadSanitizer.
This commit is contained in:
Steven Noonan
2026-07-14 14:59:12 -07:00
committed by David Reid
parent a98cc2a810
commit 22fd206db9
+13 -1
View File
@@ -71470,7 +71470,19 @@ MA_API ma_result ma_resource_manager_data_buffer_uninit(ma_resource_manager_data
} }
if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) { if (ma_resource_manager_data_buffer_result(pDataBuffer) == MA_SUCCESS) {
/* The data buffer can be deleted synchronously. */ /*
The data buffer can be deleted synchronously. Before doing so we must wait for any
in-flight load job to fully retire. The load job publishes its result code (which is what
makes us take this branch) a few instructions before it finishes referencing pDataBuffer
and increments the execution pointer. Tearing the buffer down the instant we observe
MA_SUCCESS therefore races the tail of the load job, which then reads and writes freed
memory. Wait for the execution pointer to catch up to the execution counter so that no job
is still referencing the buffer, exactly as the asynchronous path relies on for ordering.
*/
while (ma_atomic_load_32(&pDataBuffer->executionPointer) != ma_atomic_load_32(&pDataBuffer->executionCounter)) {
ma_yield();
}
return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer); return ma_resource_manager_data_buffer_uninit_internal(pDataBuffer);
} else { } else {
/* /*