Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up native resources on Unity application exit #2467

Merged
merged 6 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## vNext (TBD)

### Fixed
* \[Unity] Fixed an issue where failing to weave an assembly due to modeling errors, would only show an error in the logs once and then fail opening a Realm with `No RealmObjects. Has linker stripped them?`. Now, the weaving errors will show up on every code change/weave attempt and the runtime error will explicitly suggest manually re-running the weaver. (Issue [#2310](https://github.com/realm/realm-dotnet/issues/2310))
* \[Unity\] Fixed an issue where failing to weave an assembly due to modeling errors, would only show an error in the logs once and then fail opening a Realm with `No RealmObjects. Has linker stripped them?`. Now, the weaving errors will show up on every code change/weave attempt and the runtime error will explicitly suggest manually re-running the weaver. (Issue [#2310](https://github.com/realm/realm-dotnet/issues/2310))
* \[Unity\] Fixed an issue that would cause the app to hang on exit when using Sync. (PR [#2467](https://github.com/realm/realm-dotnet/pull/2467))
* \[Unity\] Fixed an issue that would cause the Unity editor on macOS to hang after assembly reload if the app uses Sync. (Issue [#2482](https://github.com/realm/realm-dotnet/issues/2482))

### Enhancements
* None
Expand Down
5 changes: 5 additions & 0 deletions Realm/Realm.UnityUtils/Initializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,17 @@ internal static class Initializer
{
private static int _isInitialized;

[Preserve]
public static void Initialize()
{
if (Interlocked.CompareExchange(ref _isInitialized, 1, 0) == 0)
{
InteropConfig.AddPotentialStorageFolder(FileHelper.GetStorageFolder());
Logger.Default = new UnityLogger();
UnityEngine.Application.quitting += () =>
{
NativeCommon.CleanupNativeResources("Application is exiting");
};
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions Realm/Realm/Handles/AppHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ namespace Realms.Sync
{
internal partial class AppHandle : RealmHandle
{
private static readonly List<WeakReference> _appHandles = new List<WeakReference>();

private static class NativeMethods
{
#pragma warning disable IDE1006 // Naming Styles
Expand Down Expand Up @@ -196,6 +198,12 @@ public static void Initialize()
internal AppHandle(IntPtr handle) : base(null, handle)
{
EmailPassword = new EmailPasswordApi(this);

lock (_appHandles)
{
_appHandles.RemoveAll(a => !a.IsAlive);
_appHandles.Add(new WeakReference(this));
}
}

public static AppHandle CreateApp(Native.AppConfiguration config, byte[] encryptionKey)
Expand All @@ -205,6 +213,20 @@ public static AppHandle CreateApp(Native.AppConfiguration config, byte[] encrypt
return new AppHandle(handle);
}

public static void ForceCloseHandles()
{
lock (_appHandles)
{
foreach (var weakHandle in _appHandles)
{
var appHandle = (AppHandle)weakHandle.Target;
appHandle?.Close();
}

_appHandles.Clear();
}
}

public string GetRealmPath(User user, string partition)
{
return MarshalHelpers.GetString((IntPtr buffer, IntPtr bufferLength, out bool isNull, out NativeException ex) =>
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/Handles/SharedRealmHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ public static void DeleteFiles(string path)
nativeException.ThrowIfNecessary();
}

public static void CloseAllRealms()
public static void ForceCloseNativeRealms()
{
NativeMethods.close_all_realms(out var nativeException);
nativeException.ThrowIfNecessary();
Expand Down
19 changes: 1 addition & 18 deletions Realm/Realm/InteropConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Realms.Logging;

namespace Realms
{
Expand Down Expand Up @@ -90,22 +88,7 @@ static InteropConfig()

AppDomain.CurrentDomain.DomainUnload += (_, __) =>
{
Logger.LogDefault(LogLevel.Info, "Realm: Domain is unloading, force closing all Realm instances.");

try
{
var sw = new Stopwatch();
sw.Start();

SharedRealmHandle.CloseAllRealms();

sw.Stop();
Logger.LogDefault(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms.");
}
catch (Exception ex)
{
Logger.LogDefault(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}");
}
NativeCommon.CleanupNativeResources("AppDomain is unloading");
};
}

Expand Down
29 changes: 29 additions & 0 deletions Realm/Realm/Native/NativeCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@

// file NativeCommon.cs provides mappings to common functions that don't fit the Table classes etc.
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using Realms.Logging;
using Realms.Native;
using Realms.Sync;

Expand Down Expand Up @@ -60,6 +62,33 @@ internal static void Initialize()
}
}

/// <summary>
/// **WARNING**: This will close all native Realm instances and AppHandles. This method is extremely unsafe
/// to call in any circumstance where the user might be accessing anything Realm-related. The only places
/// where we do call it is in DomainUnload and Application.quitting on Unity. We expect that at this point
/// the Application/Domain is being torn down and the user should not be interracting with Realm.
/// </summary>
public static void CleanupNativeResources(string reason)
{
try
{
Logger.LogDefault(LogLevel.Info, $"Realm: Force closing all native instances: {reason}");

var sw = new Stopwatch();
sw.Start();

AppHandle.ForceCloseHandles();
SharedRealmHandle.ForceCloseNativeRealms();

sw.Stop();
Logger.LogDefault(LogLevel.Info, $"Realm: Closed all native instances in {sw.ElapsedMilliseconds} ms.");
}
catch (Exception ex)
{
Logger.LogDefault(LogLevel.Error, $"Realm: Failed to close all native instances. You may need to restart your app. Error: {ex}");
}
}

private static void AddWindowsWrappersToPath(string relativePath, bool isUnityTarget = false)
{
try
Expand Down