Skip to content

Commit c175da2

Browse files
bugfix(audio): Prevent race condition when accessing deleted drawables from audio thread
1 parent 6266009 commit c175da2

5 files changed

Lines changed: 47 additions & 16 deletions

File tree

Core/GameEngine/Source/Common/Audio/AudioEventRTS.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656

5757
#include "GameClient/Drawable.h" // For getPosition
5858
#include "GameClient/GameClient.h" // For getDrawableByID
59+
#include "Common/ScopedMutex.h" // For ScopedMutex
5960

6061

6162
//-------------------------------------------------------------------------------------------------
@@ -741,13 +742,21 @@ const Coord3D *AudioEventRTS::getCurrentPosition()
741742
return &m_positionOfAudio;
742743

743744
case OT_Drawable:
744-
if (Drawable *draw = TheGameClient->findDrawableByID(m_drawableID))
745745
{
746-
m_positionOfAudio.set( draw->getPosition() );
747-
}
748-
else
749-
{
750-
m_ownerType = OT_Dead;
746+
// Hold the drawable lookup mutex for the duration of the find-and-read to prevent
747+
// the main thread from destroying the drawable (and freeing its memory) between
748+
// findDrawableByID() returning a non-null pointer and getPosition() being called.
749+
// This function can be invoked from the Miles audio background thread via
750+
// notifyOfAudioCompletion(), so the mutex is needed to close this race window.
751+
ScopedMutex mut(TheGameClient->getDrawableLookupMutex());
752+
if (Drawable *draw = TheGameClient->findDrawableByID(m_drawableID))
753+
{
754+
m_positionOfAudio.set( draw->getPosition() );
755+
}
756+
else
757+
{
758+
m_ownerType = OT_Dead;
759+
}
751760
}
752761
return &m_positionOfAudio;
753762

Generals/Code/GameEngine/Include/GameClient/GameClient.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class GameClient : public SubsystemInterface,
9999

100100
virtual Drawable *findDrawableByID( const DrawableID id ); ///< Given an ID, return the associated drawable
101101

102+
HANDLE getDrawableLookupMutex() const { return m_drawableLookupMutex; } ///< Returns the mutex that guards drawable hash access and drawable lifetime
103+
102104
void setDrawableIDCounter( DrawableID nextDrawableID ) { m_nextDrawableID = nextDrawableID; }
103105
DrawableID getDrawableIDCounter() { return m_nextDrawableID; }
104106

@@ -160,6 +162,7 @@ class GameClient : public SubsystemInterface,
160162

161163
Drawable *m_drawableList; ///< All of the drawables in the world
162164
DrawablePtrHash m_drawableHash; ///< Used for DrawableID lookups
165+
HANDLE m_drawableLookupMutex; ///< Guards drawable hash access and drawable lifetime against concurrent audio thread access
163166

164167
DrawableID m_nextDrawableID; ///< For allocating drawable id's
165168
DrawableID allocDrawableID(); ///< Returns a new unique drawable id

Generals/Code/GameEngine/Source/GameClient/GameClient.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
// USER INCLUDES //////////////////////////////////////////////////////////////
3535
#include "Common/ActionManager.h"
36+
#include "Common/ScopedMutex.h"
3637
#include "Common/GameEngine.h"
3738
#include "Common/GameState.h"
3839
#include "Common/GameUtility.h"
@@ -107,6 +108,8 @@ GameClient::GameClient()
107108

108109
m_nextDrawableID = (DrawableID)1;
109110
TheDrawGroupInfo = new DrawGroupInfo;
111+
112+
m_drawableLookupMutex = CreateMutex(nullptr, FALSE, nullptr);
110113
}
111114

112115
//std::vector<std::string> preloadTextureNamesGlobalHack;
@@ -223,6 +226,9 @@ GameClient::~GameClient()
223226
delete TheEva;
224227
TheEva = nullptr;
225228

229+
CloseHandle(m_drawableLookupMutex);
230+
m_drawableLookupMutex = nullptr;
231+
226232
}
227233

228234
//-------------------------------------------------------------------------------------------------
@@ -821,11 +827,13 @@ void GameClient::destroyDrawable( Drawable *draw )
821827

822828
}
823829

824-
// remove the drawable from our hash of drawables
825-
removeDrawableFromLookupTable( draw );
826-
827-
// free storage
828-
deleteInstance(draw);
830+
// Remove from hash and free storage under the mutex so that audio threads looking up
831+
// drawables by ID cannot observe a drawable pointer after its memory has been freed.
832+
{
833+
ScopedMutex mut(m_drawableLookupMutex);
834+
removeDrawableFromLookupTable( draw );
835+
deleteInstance(draw);
836+
}
829837

830838
}
831839

GeneralsMD/Code/GameEngine/Include/GameClient/GameClient.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class GameClient : public SubsystemInterface,
104104

105105
virtual Drawable *findDrawableByID( const DrawableID id ); ///< Given an ID, return the associated drawable
106106

107+
HANDLE getDrawableLookupMutex() const { return m_drawableLookupMutex; } ///< Returns the mutex that guards drawable lookup access and drawable lifetime
108+
107109
void setDrawableIDCounter( DrawableID nextDrawableID ) { m_nextDrawableID = nextDrawableID; }
108110
DrawableID getDrawableIDCounter() { return m_nextDrawableID; }
109111

@@ -181,6 +183,7 @@ class GameClient : public SubsystemInterface,
181183
Drawable *m_drawableList; ///< All of the drawables in the world
182184
// DrawablePtrHash m_drawableHash; ///< Used for DrawableID lookups
183185
DrawablePtrVector m_drawableVector;
186+
HANDLE m_drawableLookupMutex; ///< Guards drawable lookup access and drawable lifetime against concurrent audio thread access
184187

185188
DrawableID m_nextDrawableID; ///< For allocating drawable id's
186189
DrawableID allocDrawableID(); ///< Returns a new unique drawable id

GeneralsMD/Code/GameEngine/Source/GameClient/GameClient.cpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
// USER INCLUDES //////////////////////////////////////////////////////////////
3535
#include "Common/ActionManager.h"
36+
#include "Common/ScopedMutex.h"
3637
#include "Common/GameEngine.h"
3738
#include "Common/GameState.h"
3839
#include "Common/GameUtility.h"
@@ -119,6 +120,8 @@ GameClient::GameClient()
119120

120121
m_nextDrawableID = (DrawableID)1;
121122
TheDrawGroupInfo = new DrawGroupInfo;
123+
124+
m_drawableLookupMutex = CreateMutex(nullptr, FALSE, nullptr);
122125
}
123126

124127
//std::vector<std::string> preloadTextureNamesGlobalHack;
@@ -241,6 +244,9 @@ GameClient::~GameClient()
241244
delete TheSnowManager;
242245
TheSnowManager = nullptr;
243246

247+
CloseHandle(m_drawableLookupMutex);
248+
m_drawableLookupMutex = nullptr;
249+
244250
}
245251

246252
//-------------------------------------------------------------------------------------------------
@@ -884,11 +890,13 @@ void GameClient::destroyDrawable( Drawable *draw )
884890

885891
}
886892

887-
// remove the drawable from our hash of drawables
888-
removeDrawableFromLookupTable( draw );
889-
890-
// free storage
891-
deleteInstance(draw);
893+
// Remove from lookup and free storage under the mutex so that audio threads looking up
894+
// drawables by ID cannot observe a drawable pointer after its memory has been freed.
895+
{
896+
ScopedMutex mut(m_drawableLookupMutex);
897+
removeDrawableFromLookupTable( draw );
898+
deleteInstance(draw);
899+
}
892900

893901
}
894902

0 commit comments

Comments
 (0)