Skip to content

bugfix(milesaudiomanager): Prevent dangling pointer to AudioEventRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() - #2774

Merged
xezon merged 2 commits into
TheSuperHackers:mainfrom
xezon:xezon/fix-audioeventrts-threading
Aug 13, 2026
Merged

bugfix(milesaudiomanager): Prevent dangling pointer to AudioEventRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop()#2774
xezon merged 2 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 2 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 first 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 second commit 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

The PR replaces raw ownership of dynamic audio events with reference-counted sharing and safely defers delayed-loop continuation requests to the main audio update.

  • Makes DynamicAudioEventRTS a reference-counted AudioEventRTS subtype.
  • Updates audio requests, playing-audio state, and persistent audio holders to use RefCountPtr.
  • Moves delayed-loop request creation out of the Miles timer callback and closes the previously reported status/flag observation window.
  • Updates immediate-kill request matching to use the pending event’s playing handle.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
Core/GameEngine/Include/Common/AudioEventRTS.h Makes dynamic audio events reference-counted while preserving the reference count during event-data assignment.
Core/GameEngine/Include/Common/AudioRequest.h Replaces conditional raw event ownership with an independently stored reference-counted pending event.
Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h Gives playing audio shared event ownership and adds a main-update loop-rerequest signal.
Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp Transfers delayed-loop request creation to the main update, retains events through handoff, and correctly matches pending requests during immediate cancellation.
GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h Converts template audio storage to reference-counted pointers without copying live reference counts.
Core/GameEngine/Source/Common/Audio/GameAudio.cpp Allocates dynamic audio events under reference-counted ownership and updates request lifecycle handling.

Sequence Diagram

sequenceDiagram
    participant Timer as Miles timer thread
    participant Playing as PlayingAudio
    participant Main as Main audio update
    participant Queue as AudioRequest queue
    Timer->>Playing: mark rerequestOnNextUpdate
    Timer->>Playing: transition Playing to Stopping
    Main->>Playing: observe Stopping
    Main->>Queue: enqueue shared DynamicAudioEventRTS
    Main->>Playing: transition Stopping to Stopped
    Queue->>Queue: process delayed loop request
Loading

Reviews (15): Last reviewed commit: "bugfix(milesaudiomanager): Prevent dangl..." | 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 3 times, most recently 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.

@githubawn

Copy link
Copy Markdown

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@xezon

xezon commented Jun 20, 2026

Copy link
Copy Markdown
Author

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.

Break the debugger to see the callstack and where it hangs.

@Caball009

Copy link
Copy Markdown

I don't see where RefCountMTClass is used in this PR. Is this meant to be used in a follow-up PR?

Regardless, there are some thread safety issues with that class so I'd like to see that extracted to a separate PR.

@xezon

xezon commented Jun 20, 2026

Copy link
Copy Markdown
Author

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.

@Caball009

Copy link
Copy Markdown

I did use RefCountMTClass before. Then simplified code and it was no longer needed.

Fair enough.

I left it because it might be needed in future. I can remove it again.

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.

@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch from cb3b9b4 to ad3b52e Compare June 21, 2026 09:26
@xezon

xezon commented Jun 21, 2026

Copy link
Copy Markdown
Author

Please remove it from this PR at least.

First commit removed.

@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch from ad3b52e to 9c94e13 Compare June 21, 2026 10:08
Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp
@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch from 9c94e13 to 6fd2dbb Compare June 21, 2026 16:10
@xezon xezon changed the title bugfix(milesaudiomanager): Use reference counted DynamicAudioEventRTS class in AudioRequest and PlayingAudio to prevent race conditions when sharing audio event data in MilesAudioManager::startNextLoop() bugfix(milesaudiomanager): Prevent dangling pointer to AudioEventRTS in PlayingAudio when handing it over to a new AudioRequest after a call to MilesAudioManager::startNextLoop() Jun 21, 2026
@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch 2 times, most recently from 0ed037e to 7b96d91 Compare June 21, 2026 17:38
@xezon

xezon commented Jun 21, 2026

Copy link
Copy Markdown
Author

The bot had a lot of complaints here about my code. Pffft.

@Caball009
Caball009 self-requested a review June 21, 2026 20:52
Comment thread Core/GameEngine/Include/Common/GameAudio.h Outdated
Comment thread Core/Libraries/Source/WWVegas/WWLib/ref_ptr.h
Comment thread GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h Outdated
@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch from 7b96d91 to e4ab4ea Compare June 24, 2026 18:29
@xezon

xezon commented Jul 18, 2026

Copy link
Copy Markdown
Author

Needs review

@Caball009

Caball009 commented Jul 20, 2026

Copy link
Copy Markdown

Needs review

I'll do my best to review it some time this week. It's not an easy one to review.

@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch from e4ab4ea to efc2946 Compare July 28, 2026 17:21
@xezon

xezon commented Jul 28, 2026

Copy link
Copy Markdown
Author

Rebased and merge conflicts resolved.

Comment thread Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp

@Mauller Mauller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks okay overall apart from what Bobtista brought up, just some small things.
I had to stop reviewing for now since Github started breaking in weird ways.

@xezon
xezon force-pushed the xezon/fix-audioeventrts-threading branch 2 times, most recently from 070d109 to d254f25 Compare August 9, 2026 09:08

@Mauller Mauller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any glaring problems so i believe this is okay.

@xezon

xezon commented Aug 9, 2026

Copy link
Copy Markdown
Author

There are a lot of merge conflicts in drawable around the sound. Looks like that needs merging a bit first.

xezon added 2 commits August 13, 2026 22:47
…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 d254f25 to d395db7 Compare August 13, 2026 20:48
@xezon

xezon commented Aug 13, 2026

Copy link
Copy Markdown
Author

Replicated to Generals with 1 minor conflict

D:\Projects\TheSuperHackers\GeneralsGameCode>FOR /F "delims=" %b IN ('git merge-base --fork-point main') DO git diff %b  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git diff a2b5d72107d7b6dc935d7a053df2f45d8d0fcbed  1>changes.patch

D:\Projects\TheSuperHackers\GeneralsGameCode>git apply -p2 --directory=Generals --reject --whitespace=fix changes.patch
Checking patch Generals/GameEngine/Include/Common/AudioEventRTS.h...
error: Generals/GameEngine/Include/Common/AudioEventRTS.h: No such file or directory
Checking patch Generals/GameEngine/Include/Common/AudioRequest.h...
error: Generals/GameEngine/Include/Common/AudioRequest.h: No such file or directory
Checking patch Generals/GameEngine/Include/Common/GameAudio.h...
error: Generals/GameEngine/Include/Common/GameAudio.h: No such file or directory
Checking patch Generals/GameEngine/Include/Common/GameMusic.h...
error: Generals/GameEngine/Include/Common/GameMusic.h: No such file or directory
Checking patch Generals/GameEngine/Include/Common/GameSounds.h...
error: Generals/GameEngine/Include/Common/GameSounds.h: No such file or directory
Checking patch Generals/GameEngine/Source/Common/Audio/AudioRequest.cpp...
error: Generals/GameEngine/Source/Common/Audio/AudioRequest.cpp: No such file or directory
Checking patch Generals/GameEngine/Source/Common/Audio/GameAudio.cpp...
error: Generals/GameEngine/Source/Common/Audio/GameAudio.cpp: No such file or directory
Checking patch Generals/GameEngine/Source/Common/Audio/GameMusic.cpp...
error: Generals/GameEngine/Source/Common/Audio/GameMusic.cpp: No such file or directory
Checking patch Generals/GameEngine/Source/Common/Audio/GameSounds.cpp...
error: Generals/GameEngine/Source/Common/Audio/GameSounds.cpp: No such file or directory
Checking patch Generals/GameEngine/Source/Common/INI/INI.cpp...
error: Generals/GameEngine/Source/Common/INI/INI.cpp: No such file or directory
Checking patch Generals/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h...
error: Generals/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h: No such file or directory
Checking patch Generals/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp...
error: Generals/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp: No such file or directory
Checking patch Generals/Code/GameEngine/Include/Common/ThingTemplate.h...
Hunk #1 succeeded at 144 (offset 4 lines).
Hunk #2 succeeded at 172 (offset 4 lines).
error: while searching for:
        Real getBuildTime() const { return m_buildTime; }
        const PerUnitSoundMap* getAllPerUnitSounds() const { return &m_perUnitSounds; }
        void validateAudio();
        const AudioEventRTS* getAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] ? &m_audioarray.m_audio[t]->m_event : &s_audioEventNoSound; }
  Bool hasAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] != nullptr; }

        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

error: patch failed: Generals/Code/GameEngine/Include/Common/ThingTemplate.h:633
Checking patch Generals/Code/GameEngine/Include/GameClient/Drawable.h...
Checking patch Generals/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h...
Hunk #1 succeeded at 183 (offset -8 lines).
Hunk #2 succeeded at 253 (offset -10 lines).
Checking patch Generals/Code/GameEngine/Source/GameClient/Drawable.cpp...
Hunk #2 succeeded at 1281 (offset -2 lines).
Hunk #3 succeeded at 1308 (offset -2 lines).
Hunk #4 succeeded at 4058 (offset -5 lines).
Hunk #5 succeeded at 4366 (offset -5 lines).
Hunk #6 succeeded at 4396 (offset -5 lines).
Hunk #7 succeeded at 4412 (offset -5 lines).
Hunk #8 succeeded at 4426 (offset -5 lines).
Hunk #9 succeeded at 4436 (offset -5 lines).
Hunk #10 succeeded at 4444 (offset -5 lines).
Hunk #11 succeeded at 4456 (offset -5 lines).
Hunk #12 succeeded at 4497 (offset -5 lines).
Hunk #13 succeeded at 4866 (offset -5 lines).
Checking patch Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp...
Hunk #1 succeeded at 239 (offset -18 lines).
Hunk #2 succeeded at 515 (offset -83 lines).
Hunk #3 succeeded at 987 (offset -125 lines).
Applying patch Generals/Code/GameEngine/Include/Common/ThingTemplate.h with 1 reject...
Hunk #1 applied cleanly.
Hunk #2 applied cleanly.
Rejected hunk #3.
Applied patch Generals/Code/GameEngine/Include/GameClient/Drawable.h cleanly.
Applied patch Generals/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h cleanly.
Applied patch Generals/Code/GameEngine/Source/GameClient/Drawable.cpp cleanly.
Applied patch Generals/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp cleanly.

@xezon
xezon merged commit ae07b29 into TheSuperHackers:main Aug 13, 2026
16 checks passed
@xezon
xezon deleted the xezon/fix-audioeventrts-threading branch August 13, 2026 21:03
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()

5 participants