Separate managed assembly unloading and scripting ALC reinitialization

Fixes an issue with multiple managed assemblies unloading and releasing
all cached data before native resources were fully released in other
assemblies.
This commit is contained in:
2023-08-08 17:35:13 +03:00
parent f29cd1b7b8
commit 9d8105e3f3
6 changed files with 33 additions and 26 deletions

View File

@@ -903,11 +903,25 @@ namespace FlaxEngine.Interop
AssemblyLocations.Remove(assembly.FullName);
// Clear all caches which might hold references to closing assembly
// Unload native library handles associated for this assembly
string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly);
if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary))
{
NativeLibrary.Free(nativeLibrary);
loadedNativeLibraries.Remove(nativeLibraryName);
}
if (nativeLibraryName != null)
nativeLibraryPaths.Remove(nativeLibraryName);
}
[UnmanagedCallersOnly]
internal static void ReloadScriptingAssemblyLoadContext()
{
#if FLAX_EDITOR
// Clear all caches which might hold references to assemblies in collectible ALC
typeCache.Clear();
// Release all references in collectible ALC
#if FLAX_EDITOR
cachedDelegatesCollectible.Clear();
foreach (var pair in typeHandleCacheCollectible)
pair.Value.Free();
@@ -918,23 +932,13 @@ namespace FlaxEngine.Interop
foreach (var handle in fieldHandleCacheCollectible)
handle.Free();
fieldHandleCacheCollectible.Clear();
#endif
_typeSizeCache.Clear();
foreach (var pair in classAttributesCacheCollectible)
pair.Value.Free();
classAttributesCacheCollectible.Clear();
// Unload native library handles associated for this assembly
string nativeLibraryName = assemblyOwnedNativeLibraries.GetValueOrDefault(assembly);
if (nativeLibraryName != null && loadedNativeLibraries.TryGetValue(nativeLibraryName, out IntPtr nativeLibrary))
{
NativeLibrary.Free(nativeLibrary);
loadedNativeLibraries.Remove(nativeLibraryName);
}
if (nativeLibraryName != null)
nativeLibraryPaths.Remove(nativeLibraryName);
// Unload the ALC
bool unloading = true;
scriptingAssemblyLoadContext.Unloading += (alc) => { unloading = false; };
@@ -945,6 +949,7 @@ namespace FlaxEngine.Interop
InitScriptingAssemblyLoadContext();
DelegateHelpers.InitMethods();
#endif
}
[UnmanagedCallersOnly]

View File

@@ -47,7 +47,7 @@ public:
#if USE_EDITOR
// Called by Scripting in a middle of hot-reload (after unloading modules but before loading them again).
static void OnMidHotReload();
static void ReloadScriptingAssemblyLoadContext();
#endif
public:

View File

@@ -160,6 +160,7 @@ DECLARE_ENUM_OPERATORS(MTypeAttributes);
DECLARE_ENUM_OPERATORS(MMethodAttributes);
DECLARE_ENUM_OPERATORS(MFieldAttributes);
// Multiple AppDomains are superseded by AssemblyLoadContext in .NET
extern MDomain* MRootDomain;
extern MDomain* MActiveDomain;
extern Array<MDomain*, FixedAllocation<4>> MDomains;
@@ -301,11 +302,14 @@ void MCore::UnloadEngine()
#if USE_EDITOR
void MCore::OnMidHotReload()
void MCore::ReloadScriptingAssemblyLoadContext()
{
// Clear any cached class attributes (see https://github.com/FlaxEngine/FlaxEngine/issues/1108)
for (auto e : CachedClassHandles)
e.Value->_attributes.Clear();
static void* ReloadScriptingAssemblyLoadContextPtr = GetStaticMethodPointer(TEXT("ReloadScriptingAssemblyLoadContext"));
CallStaticMethod<void>(ReloadScriptingAssemblyLoadContextPtr);
}
#endif
@@ -723,16 +727,12 @@ bool MAssembly::LoadImage(const String& assemblyPath, const StringView& nativePa
bool MAssembly::UnloadImage(bool isReloading)
{
if (_handle)
if (_handle && isReloading)
{
// TODO: closing assembly on reload only is copy-paste from mono, do we need do this on .NET too?
if (isReloading)
{
LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name));
LOG(Info, "Unloading managed assembly \'{0}\' (is reloading)", String(_name));
static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly"));
CallStaticMethod<void, const void*>(CloseAssemblyPtr, _handle);
}
static void* CloseAssemblyPtr = GetStaticMethodPointer(TEXT("CloseAssembly"));
CallStaticMethod<void, const void*>(CloseAssemblyPtr, _handle);
CachedAssemblyHandles.Remove(_handle);
_handle = nullptr;

View File

@@ -716,7 +716,7 @@ void MCore::UnloadEngine()
#if USE_EDITOR
void MCore::OnMidHotReload()
void MCore::ReloadScriptingAssemblyLoadContext()
{
}

View File

@@ -61,7 +61,7 @@ void MCore::UnloadEngine()
#if USE_EDITOR
void MCore::OnMidHotReload()
void MCore::ReloadScriptingAssemblyLoadContext()
{
}

View File

@@ -706,7 +706,9 @@ void Scripting::Reload(bool canTriggerSceneReload)
modules.Clear();
_nonNativeModules.ClearDelete();
_hasGameModulesLoaded = false;
MCore::OnMidHotReload();
// Release and create a new assembly load context for user assemblies
MCore::ReloadScriptingAssemblyLoadContext();
// Give GC a try to cleanup old user objects and the other mess
MCore::GC::Collect();