Skip to content

bugfix(milesaudiomanager): Use reference counted DynamicAudioEventRTS class in AudioRequest and PlayingAudio to prevent race conditions when sharing audio event data in MilesAudioManager::startNextLoop()#2774

Open
xezon wants to merge 4 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-audioeventrts-threading

Conversation

@xezon

@xezon xezon commented Jun 7, 2026

Copy link
Copy Markdown

Merge with Rebase

This change has 3 commits to work towards fixing race conditions in MilesAudioManager concerning a shared AudioEventRTS instance in classes AudioRequest and PlayingAudio.

The first commit implements a new RefCountMTClass which is fundamentally identical to RefCountClass, except it has a thread safe counter and all the debug functionality is omitted.

The second commit adds the RefCountMTClass RefCountClass to DynamicAudioEventRTS to allow for shared ownership. All existing users of DynamicAudioEventRTS accomodate it and will now use RefCountPtr for automatic reference counting.

The third commits replaces AudioEventRTS* with RefCountPtr<DynamicAudioEventRTS> in AudioRequest and PlayingAudio to allow sharing the audio event data between them. This is needed, because ownership will be shared in function MilesAudioManager::startNextLoop (or MilesAudioManager::stopPlayingAudio), where previously AudioRequest was given the sole authority to delete the AudioEventRTS while PlayingAudio still 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 AudioEventRTS is heap allocated, not pool allocated.

TODO

@xezon xezon added this to the Stability fixes milestone Jun 7, 2026
@xezon xezon added Audio Is audio related Bug Something is not working right, typically is user facing Gen Relates to Generals ZH Relates to Zero Hour Stability Concerns stability of the runtime Minor Severity: Minor < Major < Critical < Blocker Major Severity: Minor < Major < Critical < Blocker Crash This is a crash, very bad and removed Minor Severity: Minor < Major < Critical < Blocker Stability Concerns stability of the runtime labels Jun 7, 2026
@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a race condition in MilesAudioManager::startNextLoop where PlayingAudio held a raw pointer to an AudioEventRTS that could be deleted by the AudioRequest while the MSS Timer thread was still using it. The fix introduces shared ownership via RefCountPtr<DynamicAudioEventRTS> across both AudioRequest and PlayingAudio, and defers re-request creation to the main thread via the new m_rerequestOnNextUpdate flag.

  • DynamicAudioEventRTS is refactored to directly inherit AudioEventRTS and RefCountClass, eliminating the m_event member and migrating all owners to RefCountPtr.
  • startNextLoop no longer creates an AudioRequest on the MSS Timer thread; it sets m_rerequestOnNextUpdate, which processPlayingList on the main thread reads (after the InterlockedCompareExchange fence ensures visibility) before stopping the PlayingAudio.
  • A new RefCountMTClass (atomic reference counting) is added to refcount.h/cpp but is unused in this PR — DynamicAudioEventRTS uses the existing non-atomic RefCountClass, which is safe because all Add_Ref/Release_Ref calls remain on the main thread.

Confidence Score: 5/5

Safe to merge — the core race condition is correctly fixed by deferring AudioRequest creation to the main thread, and the ref-count migration is mechanically thorough across all 28 files.

The startNextLoop fix correctly writes m_rerequestOnNextUpdate before the InterlockedCompareExchange memory barrier, so the main thread is guaranteed to see the flag set by the time it observes PS_Stopping. All RefCountPtr Add_Ref/Release_Ref operations on DynamicAudioEventRTS remain on the main thread, making the use of the non-atomic RefCountClass safe. The remaining finding (unused RefCountMTClass) is a cleanup concern that does not affect correctness.

No files require special attention — the dead RefCountMTClass code in refcount.h is the only minor concern.

Important Files Changed

Filename Overview
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Core race-condition fix: startNextLoop now signals m_rerequestOnNextUpdate (written before InterlockedCompareExchange) and defers AudioRequest creation to the main thread in processPlayingList, ensuring both PlayingAudio and the new AudioRequest share the same ref-counted event safely.
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h Replaces m_cleanupAudioEventRTS flag with m_rerequestOnNextUpdate flag and upgrades m_audioEventRTS to RefCountPtr<DynamicAudioEventRTS>; new rerequestPlayingAudio helper declared.
Core/GameEngine/Include/Common/AudioEventRTS.h DynamicAudioEventRTS flattens its nested m_event member into direct inheritance from AudioEventRTS and adds RefCountClass for shared ownership; constructors and Delete_This override look correct.
Core/GameEngine/Include/Common/AudioRequest.h Removes the union-based m_pendingEvent/m_handleToInteractOn layout and replaces AudioEventRTS* with RefCountPtr<DynamicAudioEventRTS>; m_usePendingEvent flag eliminated correctly.
Core/Libraries/Source/WWVegas/WWLib/refcount.h Adds RefCountMTClass (thread-safe, atomic ref counting) but it is not used by DynamicAudioEventRTS; constitutes dead code in the current PR.
Core/Libraries/Source/WWVegas/WWLib/refcount.cpp Implements RefCountMTClass::Add_Ref and Release_Ref using InterlockedIncrement/InterlockedDecrement — correct implementation, mirrors RefCountClass but with atomic ops.
Core/GameEngine/Source/Common/Audio/GameAudio.cpp Migrates addAudioEvent to use RefCountPtr and removes releaseAudioEventRTS; allocateAudioRequest simplified by removing the useAudioEvent parameter.
GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h AudioArray members updated to RefCountPtr<DynamicAudioEventRTS>, manual new/delete replaced by RefCountPtr; copy and assignment operators look correct.
GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp All m_ambientSound->m_event. accesses updated to m_ambientSound-> (direct inheritance), manual deleteInstance replaced with RefCountPtr::Clear(); straightforward mechanical update.
Dependencies/Utility/Utility/interlocked_adapter.h Adds InterlockedIncrement/InterlockedDecrement wrappers needed by RefCountMTClass on older Win32 SDK targets where the signatures differ.

Sequence Diagram

sequenceDiagram
    participant MT as MSS Timer Thread
    participant Main as Main Thread
    participant PA as PlayingAudio
    participant AR as AudioRequest
    participant Ev as DynamicAudioEventRTS (RefCountPtr)

    Note over PA,Ev: rc=1 (owned by PlayingAudio)

    MT->>PA: notifyOfAudioCompletion()
    PA->>MT: "startNextLoop() — delay > threshold"
    MT->>PA: "m_rerequestOnNextUpdate = true"
    MT->>PA: InterlockedCompareExchange(status, PS_Stopping)
    Note over MT: Timer thread done — no AudioRequest created here

    Main->>PA: processPlayingList() — sees m_rerequestOnNextUpdate
    Main->>AR: rerequestPlayingAudio() — copies m_audioEventRTS (Add_Ref)
    Note over Ev: rc=2 (PlayingAudio + AudioRequest)
    Main->>PA: "m_rerequestOnNextUpdate = false"
    Main->>PA: "status == PS_Stopping → stopPlayingAudio()"
    Main->>PA: releasePlayingAudioInListIfStopped() → delete PlayingAudio (Release_Ref)
    Note over Ev: rc=1 (AudioRequest only — no dangling pointer)

    Main->>AR: processRequestList() → playAudioEvent()
    AR->>Ev: assign to new PlayingAudio.m_audioEventRTS (Add_Ref)
    Note over Ev: rc=2 (AudioRequest + new PlayingAudio)
    Main->>AR: deleteInstance(req) (Release_Ref)
    Note over Ev: rc=1 (new PlayingAudio only)
Loading

Reviews (9): Last reviewed commit: "fixup! bugfix(milesaudiomanager): Preven..." | Re-trigger Greptile

Comment thread Core/Libraries/Source/WWVegas/WWLib/refcount.h Outdated
@xezon xezon force-pushed the xezon/fix-audioeventrts-threading branch 3 times, most recently from c9bfbb0 to 049a95b Compare June 8, 2026 20:09
@xezon

xezon commented Jun 9, 2026

Copy link
Copy Markdown
Author

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 PlayingAudio to tell main thread to create a new audio request when stopping the playing audio. This simplifies the whole thing.

The RefCountMTClass is now no longer used, but we can keep it anyway for future use cases.

@xezon xezon force-pushed the xezon/fix-audioeventrts-threading branch from 2ee51b3 to 22f58cd Compare June 9, 2026 19:37
@xezon xezon force-pushed the xezon/fix-audioeventrts-threading branch from 22f58cd to 1d3692e Compare June 13, 2026 11:34
…entRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() (#2774)
@xezon xezon force-pushed the xezon/fix-audioeventrts-threading branch from 730b739 to cb3b9b4 Compare June 14, 2026 08:57
@xezon

xezon commented Jun 14, 2026

Copy link
Copy Markdown
Author

Polished. Ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Audio Is audio related Bug Something is not working right, typically is user facing Crash This is a crash, very bad Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash in MilesAudioManager::stopPlayingAudio()

1 participant