bugfix(milesaudiomanager): Prevent dangling pointer to AudioEventRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop()#2774
Conversation
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Include/Common/AudioEventRTS.h | DynamicAudioEventRTS now directly inherits AudioEventRTS and RefCountClass, removes the m_event member, adds forwarding constructors, and overrides Delete_This() for memory-pool correctness. |
| Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h | PlayingAudio updated: m_audioEventRTS is RefCountPtr, m_cleanupAudioEventRTS replaced with volatile m_rerequestOnNextUpdate; new helper declarations added. |
| Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp | Core fix: rerequestPlayingAudio/rerequestPlayingAudioWhenSignalled added; startNextLoop delay path signals the main thread instead of transferring raw ownership; killAudioEventImmediately and isCurrentlyPlaying correctly use req->m_pendingEvent non-null as the AR_Play discriminator. |
| Core/GameEngine/Include/Common/AudioRequest.h | Union replaced by separate m_pendingEvent (RefCountPtr) and m_handleToInteractOn fields; m_usePendingEvent removed; explicit constructor added initializing all members. |
| GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h | AudioArray updated to hold RefCountPtr; copy-ctor allocates new instances; operator= re-uses existing in-place via compiler-generated copy-assign which correctly skips RefCountClass via its no-op operator=. |
| Core/GameEngine/Source/Common/Audio/GameAudio.cpp | addAudioEvent creates DynamicAudioEventRTS via newInstance with Create_NoAddRef; early-exit paths let the local RefCountPtr destructor handle cleanup automatically. |
| Core/GameEngine/Source/Common/Audio/GameSounds.cpp | addAudioEvent return type changed from void to Bool; Create_AddRef used when assigning to AudioRequest::m_pendingEvent. Correct. |
| GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp | m_ambientSound updated to RefCountPtr; all manual deleteInstance/nullptr assignments replaced with .Clear() calls; m_event. accesses replaced with direct member access. |
| Core/Libraries/Source/WWVegas/WWLib/ref_ptr.h | Only change is fixing the wwdebug.h include path to use the subdirectory prefix. No functional changes. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant MT as Main Thread
participant PA as PlayingAudio
participant DAERTS as DynamicAudioEventRTS (refcount)
participant AR as AudioRequest
participant MSS as MSS Timer Thread
Note over PA,DAERTS: audio starts - refcount=1
PA->>DAERTS: "m_audioEventRTS RefCountPtr rc=1"
MSS->>PA: "notifyOfAudioCompletion -> startNextLoop"
Note over MSS: getDelay() > threshold
MSS->>PA: "m_rerequestOnNextUpdate = true"
MSS->>PA: "InterlockedCmpXchg -> PS_Stopping"
MT->>PA: processPlayingList sees PS_Stopping
MT->>MT: rerequestPlayingAudio(playing)
MT->>AR: "req->m_pendingEvent = playing->m_audioEventRTS"
Note over DAERTS: refcount = 2 (PA + AR)
MT->>PA: "stopPlayingAudio -> releaseMilesHandles"
MT->>PA: "releasePlayingAudio -> delete playing"
Note over DAERTS: refcount = 1 (only AR)
MT->>AR: "processRequest -> playAudioEvent"
AR->>PA: "new PlayingAudio m_audioEventRTS = req->m_pendingEvent"
Note over DAERTS: refcount = 2 (new PA + AR)
MT->>AR: deleteInstance(req)
Note over DAERTS: refcount = 1 (new PA only)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant MT as Main Thread
participant PA as PlayingAudio
participant DAERTS as DynamicAudioEventRTS (refcount)
participant AR as AudioRequest
participant MSS as MSS Timer Thread
Note over PA,DAERTS: audio starts - refcount=1
PA->>DAERTS: "m_audioEventRTS RefCountPtr rc=1"
MSS->>PA: "notifyOfAudioCompletion -> startNextLoop"
Note over MSS: getDelay() > threshold
MSS->>PA: "m_rerequestOnNextUpdate = true"
MSS->>PA: "InterlockedCmpXchg -> PS_Stopping"
MT->>PA: processPlayingList sees PS_Stopping
MT->>MT: rerequestPlayingAudio(playing)
MT->>AR: "req->m_pendingEvent = playing->m_audioEventRTS"
Note over DAERTS: refcount = 2 (PA + AR)
MT->>PA: "stopPlayingAudio -> releaseMilesHandles"
MT->>PA: "releasePlayingAudio -> delete playing"
Note over DAERTS: refcount = 1 (only AR)
MT->>AR: "processRequest -> playAudioEvent"
AR->>PA: "new PlayingAudio m_audioEventRTS = req->m_pendingEvent"
Note over DAERTS: refcount = 2 (new PA + AR)
MT->>AR: deleteInstance(req)
Note over DAERTS: refcount = 1 (new PA only)
Reviews (14): Last reviewed commit: "bugfix(milesaudiomanager): Prevent dangl..." | 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. |
730b739 to
cb3b9b4
Compare
|
Polished. Ready for review. |
|
I've been debugging this PR, and it sometimes softlocks when I exit near the factory and town in Alpine Assault. I didn't see anything strange in the debugger, but having a heightened camera increases the chances that it hangs. I think I found a race condition that explains it. |
|
|
||
| if (playing->m_rerequestOnNextUpdate) { | ||
| rerequestPlayingAudio(playing); | ||
| playing->m_rerequestOnNextUpdate = false; |
There was a problem hiding this comment.
Since processPlayingList() iterates without a lock, could a Miles background callback be modifying this object's state at the exact same time we read/overwrite it?
There was a problem hiding this comment.
I don't think so, because this bool is set true only in that miles callback and the miles callback will not be called again.
Break the debugger to see the callstack and where it hangs. |
|
I don't see where Regardless, there are some thread safety issues with that class so I'd like to see that extracted to a separate PR. |
|
I did use RefCountMTClass before. Then simplified code and it was no longer needed. I left it because it might be needed in future. I can remove it again. |
Fair enough.
Please remove it from this PR at least. A reference counting class is quite tricky stuff for multi-threaded purposes. It should be added in a separate PR if we ever need it. |
cb3b9b4 to
ad3b52e
Compare
First commit removed. |
ad3b52e to
9c94e13
Compare
9c94e13 to
6fd2dbb
Compare
6fd2dbb to
0ed037e
Compare
0ed037e to
7b96d91
Compare
|
The bot had a lot of complaints here about my code. Pffft. |
| { | ||
| if (that.m_audio[i]) | ||
| m_audio[i] = newInstance(DynamicAudioEventRTS)(*that.m_audio[i]); | ||
| m_audio[i] = RefCountPtr<DynamicAudioEventRTS>::Create_NoAddRef(newInstance(DynamicAudioEventRTS)(*that.m_audio[i])); |
There was a problem hiding this comment.
Why doesn't this do m_audio[i] = that.m_audio[i];?
There was a problem hiding this comment.
Because that would be not identical to what it did before.
…in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() (#2774)
7b96d91 to
e4ab4ea
Compare
Merge with Rebase
This change has 2 commits to work towards fixing race conditions in
MilesAudioManagerconcerning a sharedAudioEventRTSinstance in classesAudioRequestandPlayingAudio.The first commit implements a newRefCountMTClasswhich is fundamentally identical toRefCountClass, except it has a thread safe counter and all the debug functionality is omitted.The first commit adds the
RefCountMTClassRefCountClasstoDynamicAudioEventRTSto allow for shared ownership. All existing users ofDynamicAudioEventRTSaccomodate it and will now useRefCountPtrfor automatic reference counting.The second commit 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