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:
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -716,7 +716,7 @@ void MCore::UnloadEngine()
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void MCore::OnMidHotReload()
|
||||
void MCore::ReloadScriptingAssemblyLoadContext()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ void MCore::UnloadEngine()
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void MCore::OnMidHotReload()
|
||||
void MCore::ReloadScriptingAssemblyLoadContext()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user