Hey Epic Friends!
So, USynthSound::OnBeginGenerate is called on the audio mixer thread. It currently guards OwningSynthComponent with ensure(OwningSynthComponent.IsValid()). When a USynthComponent is destroyed on the game thread between the AudioComponent->Play() call and the audio thread processing InitSource → OnBeginGenerate, OwningSynthComponent is already marked PendingKill and the ensure fires, boom!
Stack (audio mixer thread):
USynthSound::OnBeginGenerate (SynthComponent.cpp:39) <-- ENSURE
Audio::FMixerSourceBuffer::OnBeginGenerate
Audio::FMixerSourceManager::InitSource [audio thread command queue]
Audio::FMixerSourceManager::PumpCommandQueue
Audio::FMixerDevice::OnProcessAudioStream
Audio::IAudioMixerPlatformInterface::RunInternal
Root cause:
OnBeginGenerate, OnGeneratePCMAudio, and OnEndGenerate all run on the audio thread and all access OwningSynthComponent (a TWeakObjectPtr). The latter two already handle the race silently:
// OnGeneratePCMAudio (line 55) -- correct:
// Mark pending kill can null this out on the game thread in rare cases.
if (!OwningSynthComponent.IsValid())
return 0;
// OnEndGenerate (line 67) -- correct:
if (OwningSynthComponent.IsValid())
OwningSynthComponent->OnEndGenerate();
// OnBeginGenerate (line 39) -- inconsistent:
if (ensure(OwningSynthComponent.IsValid())) // <-- fires under the same race
OwningSynthComponent->OnBeginGenerate();
So we’re thinking about this little fix:
void USynthSound::OnBeginGenerate()
{
// OwningSynthComponent can be marked PendingKill on the game thread in rare cases.
if (USynthComponent* Comp = OwningSynthComponent.Get())
{
Comp->OnBeginGenerate();
}
}
This matches the pattern already used in OnGeneratePCMAudio and OnEndGenerate. It’s doesn’t technically seem perfect though, because in a worst case scenario, the main thread might destroy the OwningSynthComponent right after the if check? But due to the way the current GC works, that seems very very very unlikely?
So some questions-
Is there a stronger fix planned (e.g. ensuring the audio source is fully stopped before the USynthComponent reaches BeginDestroy, eliminating the race entirely)?
Is the GC-freeing-of-memory window also considered safe given BeginDestroy → Stop() ordering, or is there a scenario where the audio thread can hold a raw Comp pointer past object destruction?
thanks!!
Josh
[Attachment Removed]