bugfix(milesaudiomanager): Use reference counted DynamicAudioEventRTS class in AudioRequest and PlayingAudio to prevent race conditions when sharing audio event data in MilesAudioManager::startNextLoop()#2774
Conversation
|
| Filename | Overview |
|---|---|
| Core/Libraries/Source/WWVegas/WWLib/refcount.h | Adds RefCountMTClass with thread-safe interlocked ref-counting and a correct copy-assignment operator (takes const RefCountMTClass&). RefCountMTClass is defined here but not yet used by DynamicAudioEventRTS (which uses RefCountClass); available for future use. |
| Core/Libraries/Source/WWVegas/WWLib/refcount.cpp | Implements RefCountMTClass::Add_Ref and Release_Ref using InterlockedIncrement/Decrement. Calls Delete_This when ref hits zero, consistent with RefCountClass pattern. |
| Core/GameEngine/Include/Common/AudioEventRTS.h | DynamicAudioEventRTS now directly inherits AudioEventRTS (instead of containing it as m_event) and adds RefCountClass for shared ownership. Delete_This override correctly routes to the memory pool via deleteInstance. |
| Core/GameEngine/Include/Common/AudioRequest.h | Replaces the raw-pointer/union (AudioEventRTS*, AudioHandle) with RefCountPtr m_pendingEvent and a separate AudioHandle. Removes the m_usePendingEvent flag, simplifying lifetime management. |
| Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h | PlayingAudio::m_audioEventRTS changed to RefCountPtr; m_cleanupAudioEventRTS replaced by m_rerequestOnNextUpdate. New rerequestPlayingAudio helper declared. m_rerequestOnNextUpdate should be volatile for cross-thread visibility. |
| Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp | Core race-condition fix: startNextLoop now defers rerequest via m_rerequestOnNextUpdate flag instead of directly creating a new AudioRequest from the timer thread. rerequestPlayingAudio copies the RefCountPtr on the main thread, avoiding the unsafe cross-thread ownership transfer. |
| Core/GameEngine/Source/Common/Audio/GameAudio.cpp | addAudioEvent now allocates DynamicAudioEventRTS via newInstance and wraps it in RefCountPtr::Create_NoAddRef; releaseAudioEventRTS removed; SoundManager::addAudioEvent now returns Bool to signal ownership transfer. |
| GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h | AudioArray::m_audio changed to RefCountPtr[]; ctor/dtor simplified; copy constructor and operator= updated to use Create_NoAddRef for new instances while leaving the ref-count no-op assignment in place for existing slots. |
| Dependencies/Utility/Utility/interlocked_adapter.h | Adds InterlockedIncrement/Decrement wrappers inside the existing #if _MSC_VER < 1300 guard. The (long*) cast intentionally strips volatile to resolve to the pre-1300 MSVC Windows API signature (PLONG, no volatile), avoiding infinite recursion. |
Sequence Diagram
sequenceDiagram
participant MT as Main Thread
participant TT as MSS Timer Thread
participant DA as DynamicAudioEventRTS
Note over MT: processRequestList → playAudioEvent
MT->>DA: "Create_NoAddRef (refs=1)"
MT->>DA: "RefCountPtr copy into PlayingAudio (refs=2)"
MT->>DA: "RefCountPtr in AudioRequest released (refs=1)"
Note over TT: notifyOfAudioCompletion → startNextLoop
TT->>DA: Peek() — no ref change
TT-->>MT: "sets m_rerequestOnNextUpdate=true, InterlockedCAS(m_status, PS_Stopping)"
Note over MT: processPlayingList (next frame)
MT->>DA: "rerequestPlayingAudio: RefCountPtr copy into new AudioRequest (refs=2)"
MT->>MT: "stopPlayingAudio → releasePlayingAudio, PlayingAudio deleted (refs=1)"
MT->>DA: "playAudioEvent: new PlayingAudio takes ownership (refs=1)"
Reviews (8): Last reviewed commit: "fixup! bugfix(milesaudiomanager): Preven..." | Re-trigger Greptile
c9bfbb0 to
049a95b
Compare
|
I revisited the implementation and added new fixup commits to simplify it, because I noticed that we can avoid adding the deferred audio requests container by using a new flag in The RefCountMTClass is now no longer used, but we can keep it anyway for future use cases. |
2ee51b3 to
22f58cd
Compare
…in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() (#2774)
22f58cd to
1d3692e
Compare
…entRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() (#2774)
Merge with Rebase
This change has 3 commits to work towards fixing race conditions in
MilesAudioManagerconcerning a sharedAudioEventRTSinstance in classesAudioRequestandPlayingAudio.The first commit implements a new
RefCountMTClasswhich is fundamentally identical toRefCountClass, except it has a thread safe counter and all the debug functionality is omitted.The second commit adds the
RefCountMTClassRefCountClasstoDynamicAudioEventRTSto allow for shared ownership. All existing users ofDynamicAudioEventRTSaccomodate it and will now useRefCountPtrfor automatic reference counting.The third commits replaces
AudioEventRTS*withRefCountPtr<DynamicAudioEventRTS>inAudioRequestandPlayingAudioto allow sharing the audio event data between them. This is needed, because ownership will be shared in functionMilesAudioManager::startNextLoop(orMilesAudioManager::stopPlayingAudio), where previouslyAudioRequestwas given the sole authority to delete theAudioEventRTSwhilePlayingAudiostill kept a pointer to it. Now both classes need to release their reference count before the audio event data is deleted.This likely was also a problem in retail, because
AudioEventRTSis heap allocated, not pool allocated.TODO