Files
sbox-public/engine/Sandbox.Engine/Resources/AsyncResourceLoader.cs
Lorenz Junglas 35592e576b Ensure AsyncResourceLoader does not access the resource system during shutdown (#3904)
Speculative fix for the EnqueueResourceManifest crash reported on Sentry.

The crash occurs in a thread-interlocked compare-exchange operation:

```
ThreadInterlockedCompareExchange64
ThreadInterlockedCompareExchangePointer
CTSQueue<T>::InterlockedCompareExchangeNode
CTSQueue<T>::Push
CTSQueue<T>::PushItem
CResourceSystem::EnqueueResourceManifest
CResourceSystem::CreateResourceManifestInternal
Exports::g_pRsrcSystm_LoadResourceInManifest
```

The most likely cause of a ThreadInterlock failure is a null pointer. AFAIK the resource queue only becomes null during shutdown, so this crash is probably a race condition where managed code attempts to load a resource while the engine is shutting down.
2026-01-27 13:53:33 +00:00

65 lines
1.4 KiB
C#

using System.Threading;
namespace Sandbox;
//
// This is just a test right now
//
class AsyncResourceLoader : IDisposable
{
HResourceManifest manifest;
AsyncResourceLoader( HResourceManifest manifest )
{
this.manifest = manifest;
}
~AsyncResourceLoader()
{
if ( manifest.IsValid && !NativeEngine.g_pResourceSystem.IsShuttingDown() )
{
MainThread.QueueDispose( this );
}
}
public async Task WaitForLoad( CancellationToken token = default )
{
// does this shit itself if the resource is missing?
// does it handle compiling okay?
while ( !NativeEngine.g_pResourceSystem.IsShuttingDown() && !NativeEngine.g_pResourceSystem.IsManifestLoaded( manifest ) )
{
await Task.Yield();
token.ThrowIfCancellationRequested();
if ( NativeEngine.g_pResourceSystem.IsShuttingDown() )
return;
}
}
public void Dispose()
{
if ( manifest.IsValid && !NativeEngine.g_pResourceSystem.IsShuttingDown() )
{
ThreadSafe.AssertIsMainThread();
NativeEngine.g_pResourceSystem.DestroyResourceManifest( manifest );
}
manifest = default;
GC.SuppressFinalize( this );
}
static public AsyncResourceLoader Load( string filename )
{
ThreadSafe.AssertIsMainThread();
if ( NativeEngine.g_pResourceSystem.IsShuttingDown() )
return default;
var manifest = NativeEngine.g_pResourceSystem.LoadResourceInManifest( filename );
if ( manifest.IsNull ) return default;
return new AsyncResourceLoader( manifest );
}
}