mirror of
https://github.com/mackron/miniaudio.git
synced 2026-07-22 12:52:43 +02:00
resource_manager: fix data buffer node teardown racing async page job tail
ma_resource_manager_data_buffer_node_unacquire frees the node synchronously when it observes a non-MA_BUSY result (the "sound isn't loading" branch). But a decoded-paged node publishes result == MA_SUCCESS as soon as it is playable, while ma_job_process__resource_manager__page_data_buffer_node jobs keep running to decode the remaining pages. Those jobs reference the node and increment its executionPointer at their tail, several instructions after the result becomes observable. A concurrent unacquire that sees a non-BUSY result therefore frees the node out from under the page job tail, which then reads and atomically increments freed (and possibly recycled) memory. The node carries the same order / executionPointer ticket scheme as the data buffer, and the MA_BUSY branch already relies on it (it posts an ordered FREE_DATA_BUFFER_NODE job). Only the synchronous shortcut skipped it. Make it wait for executionPointer to catch up to executionCounter before freeing, so no job is still referencing the node -- the same fix as ma_resource_manager_data_buffer_uninit for the buffer level, one level down. Caught by ThreadSanitizer.
This commit is contained in:
committed by
David Reid
parent
e93ec1d340
commit
f73efd2d08
+14
-1
@@ -71164,7 +71164,20 @@ stage2:
|
||||
/* Threading is enabled. The job queue will deal with the rest of the cleanup from here. */
|
||||
}
|
||||
} else {
|
||||
/* The sound isn't loading so we can just free the node here. */
|
||||
/*
|
||||
The node isn't loading, so it can be freed synchronously here. But first wait for any
|
||||
in-flight job to fully retire. A decoded-paged node reports MA_SUCCESS as soon as it is
|
||||
playable, while page_data_buffer_node jobs keep decoding the remaining pages and only
|
||||
increment the execution pointer at their tail. Freeing the node the instant we observe a
|
||||
non-BUSY result therefore races the tail of such a job, which then reads/writes freed
|
||||
memory. Wait for the execution pointer to catch up to the execution counter so no job is
|
||||
still referencing the node, exactly as the asynchronous free path relies on for ordering.
|
||||
(Mirrors the data-buffer fix in ma_resource_manager_data_buffer_uninit.)
|
||||
*/
|
||||
while (ma_atomic_load_32(&pDataBufferNode->executionPointer) != ma_atomic_load_32(&pDataBufferNode->executionCounter)) {
|
||||
ma_yield();
|
||||
}
|
||||
|
||||
ma_resource_manager_data_buffer_node_free(pResourceManager, pDataBufferNode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user