WinDualShock plugin crash updating audio devices

Callstack: (Edited in thought I had included it originally]

<ShippingExe>!TSet<TTuple<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1> >,TDefaultMapHashableKeyFuncs<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1>,0>,FDefaultSetAllocator>::RemoveImpl /Engine/Source/Runtime/Core/Public/Containers/Set.h(1024)
<ShippingExe>!TSet<TTuple<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1> >,TDefaultMapHashableKeyFuncs<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1>,0>,FDefaultSetAllocator>::Remove /Engine/Source/Runtime/Core/Public/Containers/Set.h(1056)
<ShippingExe>!TMapBase<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1>,FDefaultSetAllocator,TDefaultMapHashableKeyFuncs<FDeviceKey,TSharedRef<IWinDualShockAudioDevice,1>,0> >::Remove /Engine/Source/Runtime/Core/Public/Containers/Map.h(496)
<ShippingExe>!FWinDualShock::UpdateAudioDevices /Engine/Plugins/Runtime/Windows/WinDualShock/Source/WinDualShock/Private/WinDualShock.cpp(226)
<ShippingExe>!FWindowsApplication::PollGameDeviceState /Engine/Source/Runtime/ApplicationCore/Private/Windows/WindowsApplication.cpp(2870)
<ShippingExe>!FEngineLoop::Tick /Engine/Source/Runtime/Launch/Private/LaunchEngineLoop.cpp(5866)
<ShippingExe>!EngineTick /Engine/Source/Runtime/Launch/Private/Launch.cpp(69)
<ShippingExe>!GuardedMain /Engine/Source/Runtime/Launch/Private/Launch.cpp(188)
<ShippingExe>!GuardedMainWrapper /Engine/Source/Runtime/Launch/Private/Windows/LaunchWindows.cpp(126)
<ShippingExe>!LaunchWindowsStartup /Engine/Source/Runtime/Launch/Private/Windows/LaunchWindows.cpp(284)
<ShippingExe>!WinMain /Engine/Source/Runtime/Launch/Private/Windows/LaunchWindows.cpp(325)
<ShippingExe>!invoke_main D:/a/_work/1/s/src/vctools/crt/vcstartup/src/startup/exe_common.inl(102)

Repro: No known reproduction, 5 users have hit this about 20s in on launch of title.

Looking at a crash dump:

Dump looks like the DeviceMap’s Hash lookup had potentially garbage data in it but unsure why as the minidump data suggests it shouldn’t have.

Map looks like it has one entry (expected)

Logic looks like it checked the Hash storage in slot 0 (expected)

Logic looks like it fetched the index of 0x9e8d2a40 (unexpected)

Minidump I think has that Hash storage in it, but it should have returned 0.

Map address: 0x0000026d9fc18e08

Hash storage inline memory: 0x0000026d9fc18e40

Value at 0x0000026d9fc18e40: 0

Expected value of NextElementId->Index: 0

Speculation: While looking at the code it looks like the DeviceMap may be altered by both the main thread as seen in the callstack and by the AudioThread. My crashes do not capture AudioThread in the act however, but it looks like the AudioThread may alter the DeviceMap via a call to GetAudioDevice where the device hasn’t been added yet by the main thread.

Potential AudioThread calls:

FMixerSubmix::Init > FMixerSubmix::InitInternal > FMixerSubmix::SetupEndpoint > InFactory->CreateNewEndpointInstance > GetAudioDevice

Is this speculation correct does this need some form of multi-threaded access protection?

Yes, I agree it does look like that `DeviceMap` needs some protection. `CreateNewEndpointInstance(…)` has some interesting code for delaying creation of devices as well in the case where the audio device does not exist already.

I think adding protection around the DeviceMap is likely the shortest path to success here.

Ok, thank you for sanity checking my assumption. I am just going to add a critical section for us locally. I think this is VERY timing specific, we hadn’t seen it on previous builds at all so I likely won’t have any real way to know if it is fixed but better safe than sorry.

Thanks David. Let me know if you hit any more crashes with that fix in.