diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d3c9704f..efa0fb230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes +- The app hang detection now correctly pauses and resumes when the game moves between background and foreground to prevent false positive events ([#2788](https://github.com/getsentry/sentry-unity/pull/2788)) - Migrated the native support for iOS and macOS to use the Obj-C flavour of `sentry-cocoa` ([#2767](https://github.com/getsentry/sentry-unity/pull/2767)) - The SDK now correctly synchronizes the `Environment` set on the `Scope` events coming from the native layer ([#2764](https://github.com/getsentry/sentry-unity/pull/2764)) @@ -21,9 +22,9 @@ - Bump .NET SDK from v6.5.0-33-g0140be0a to v6.8.0 ([#2761](https://github.com/getsentry/sentry-unity/pull/2761), [#2762](https://github.com/getsentry/sentry-unity/pull/2762), [#2776](https://github.com/getsentry/sentry-unity/pull/2776)) - [changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md#680) - [diff](https://github.com/getsentry/sentry-dotnet/compare/6.5.0-33-g0140be0a...6.8.0) -- Bump Native SDK from v0.15.3 to v0.16.0 ([#2769](https://github.com/getsentry/sentry-unity/pull/2769), [#2779](https://github.com/getsentry/sentry-unity/pull/2779)) - - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0160) - - [diff](https://github.com/getsentry/sentry-native/compare/0.15.3...0.16.0) +- Bump Native SDK from v0.15.3 to v0.16.1 ([#2769](https://github.com/getsentry/sentry-unity/pull/2769), [#2779](https://github.com/getsentry/sentry-unity/pull/2779), [#2788](https://github.com/getsentry/sentry-unity/pull/2788)) + - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0161) + - [diff](https://github.com/getsentry/sentry-native/compare/0.15.3...0.16.1) - Bump Cocoa SDK from v9.21.0 to v9.23.0 ([#2767](https://github.com/getsentry/sentry-unity/pull/2767)) - [changelog](https://github.com/getsentry/sentry-cocoa/blob/main/CHANGELOG.md#9230) - [diff](https://github.com/getsentry/sentry-cocoa/compare/9.21.0...9.23.0) diff --git a/modules/sentry-java b/modules/sentry-java index b01b159cc..0d5e7c418 160000 --- a/modules/sentry-java +++ b/modules/sentry-java @@ -1 +1 @@ -Subproject commit b01b159cce519c2da9db6303c8e9b64cdefd7b51 +Subproject commit 0d5e7c41803abe84c5d50cde7581517358c3641c diff --git a/modules/sentry-native b/modules/sentry-native index a99d64efb..a185ce80b 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit a99d64efb8d5c614bbcfdab7c7dc737530e5ef7d +Subproject commit a185ce80ba2416b0a0bb04b4ee8f11f1117ae08f diff --git a/src/Sentry.Unity.Android/SentryNative.cs b/src/Sentry.Unity.Android/SentryNative.cs index 6709f8e10..0b8f32dc7 100644 --- a/src/Sentry.Unity.Android/SentryNative.cs +++ b/src/Sentry.Unity.Android/SentryNative.cs @@ -28,6 +28,8 @@ public static class SentryNative internal static void AppHangHeartbeat() => AppHangHeartbeatStrategy(); + internal static void AppHangPause() => AppHangPauseStrategy(); + // libsentry.io [DllImport("sentry")] private static extern void sentry_reinstall_backend(); @@ -35,7 +37,11 @@ public static class SentryNative [DllImport("sentry")] private static extern void sentry_app_hang_heartbeat(); + [DllImport("sentry")] + private static extern void sentry_app_hang_pause(); + // Testing internal static Action ReinstallSentryNativeBackendStrategy = sentry_reinstall_backend; internal static Action AppHangHeartbeatStrategy = sentry_app_hang_heartbeat; + internal static Action AppHangPauseStrategy = sentry_app_hang_pause; } diff --git a/src/Sentry.Unity.Android/SentryNativeAndroid.cs b/src/Sentry.Unity.Android/SentryNativeAndroid.cs index 4a33f64f7..005aed753 100644 --- a/src/Sentry.Unity.Android/SentryNativeAndroid.cs +++ b/src/Sentry.Unity.Android/SentryNativeAndroid.cs @@ -77,7 +77,9 @@ public static void Configure(SentryUnityOptions options) if (options.NativeAppHangTrackingEnabled && options.NdkIntegrationEnabled) { Logger?.LogDebug("Starting the app-hang heartbeat coroutine."); - SentryMonoBehaviour.Instance.StartAppHangHeartbeat(SentryNative.AppHangHeartbeat); + SentryMonoBehaviour.Instance.StartAppHangHeartbeat( + SentryNative.AppHangHeartbeat, + SentryNative.AppHangPause); Logger?.LogDebug("Disabling the C# ANR watchdog - sentry-native handles app hang detection."); options.RemoveIntegration(); } diff --git a/src/Sentry.Unity.Native/SentryNative.cs b/src/Sentry.Unity.Native/SentryNative.cs index 2eb1c80ed..c255ada10 100644 --- a/src/Sentry.Unity.Native/SentryNative.cs +++ b/src/Sentry.Unity.Native/SentryNative.cs @@ -97,7 +97,9 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf if (options.NativeAppHangTrackingEnabled) { Logger?.LogDebug("Starting the app-hang heartbeat coroutine."); - SentryMonoBehaviour.Instance.StartAppHangHeartbeat(SentryNativeBridge.AppHangHeartbeat); + SentryMonoBehaviour.Instance.StartAppHangHeartbeat( + SentryNativeBridge.AppHangHeartbeat, + SentryNativeBridge.AppHangPause); // sentry-native handles app-hang detection on the desktop platforms. Where it is effective, skip the // C# ANR watchdog so a hang isn't reported twice (mirrors the iOS/sentry-cocoa behavior). diff --git a/src/Sentry.Unity.Native/SentryNativeBridge.cs b/src/Sentry.Unity.Native/SentryNativeBridge.cs index 3d5a44457..343521ed3 100644 --- a/src/Sentry.Unity.Native/SentryNativeBridge.cs +++ b/src/Sentry.Unity.Native/SentryNativeBridge.cs @@ -163,6 +163,8 @@ internal static string GetDatabasePath(SentryUnityOptions options, IApplication? internal static void AppHangHeartbeat() => sentry_app_hang_heartbeat(); + internal static void AppHangPause() => sentry_app_hang_pause(); + // libsentry.so [DllImport(SentryLib)] private static extern IntPtr sentry_options_new(); @@ -211,6 +213,9 @@ internal static string GetDatabasePath(SentryUnityOptions options, IApplication? [DllImport(SentryLib)] private static extern void sentry_options_set_app_hang_timeout(IntPtr options, ulong timeout); + [DllImport(SentryLib)] + private static extern void sentry_app_hang_pause(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true)] private delegate void sentry_logger_function_t(int level, IntPtr message, IntPtr argsAddress, IntPtr userData); diff --git a/src/Sentry.Unity/SentryMonoBehaviour.AppHang.cs b/src/Sentry.Unity/SentryMonoBehaviour.AppHang.cs index 80d984f4d..44e92135c 100644 --- a/src/Sentry.Unity/SentryMonoBehaviour.AppHang.cs +++ b/src/Sentry.Unity/SentryMonoBehaviour.AppHang.cs @@ -14,6 +14,7 @@ public partial class SentryMonoBehaviour private static readonly TimeSpan AppHangHeartbeatInterval = TimeSpan.FromSeconds(1); private Coroutine? _appHangHeartbeat; + private Action? _appHangPause; /// /// Starts the app-hang heartbeat on the main thread at a fixed 1-second interval. Arming is @@ -23,6 +24,18 @@ public partial class SentryMonoBehaviour public Coroutine StartAppHangHeartbeat(Action heartbeat) => StartAppHangHeartbeat(heartbeat, AppHangHeartbeatInterval); + internal Coroutine StartAppHangHeartbeat(Action heartbeat, Action pause) + { + if (_appHangPause is not null) + { + ApplicationPausing -= _appHangPause; + } + + _appHangPause = pause; + ApplicationPausing += pause; + return StartAppHangHeartbeat(heartbeat, AppHangHeartbeatInterval); + } + // Internal overload so tests can use a short interval. internal Coroutine StartAppHangHeartbeat(Action heartbeat, TimeSpan interval) { diff --git a/test/Sentry.Unity.Android.Tests/SentryNativeAndroidTests.cs b/test/Sentry.Unity.Android.Tests/SentryNativeAndroidTests.cs index e63a9654a..db5b37d7c 100644 --- a/test/Sentry.Unity.Android.Tests/SentryNativeAndroidTests.cs +++ b/test/Sentry.Unity.Android.Tests/SentryNativeAndroidTests.cs @@ -129,6 +129,25 @@ public void AppHangHeartbeat_InvokesNativeHeartbeat() } } + [Test] + public void AppHangPause_InvokesNativePause() + { + var pauseCalled = false; + var originalStrategy = Interlocked.Exchange(ref SentryNative.AppHangPauseStrategy, + () => pauseCalled = true); + + try + { + SentryNative.AppHangPause(); + + Assert.True(pauseCalled); + } + finally + { + Interlocked.Exchange(ref SentryNative.AppHangPauseStrategy, originalStrategy); + } + } + [Test] public void Configure_InstallationIdReturned_SetsDefaultUserId() { diff --git a/test/Sentry.Unity.Tests/SentryMonoBehaviourAppHangTests.cs b/test/Sentry.Unity.Tests/SentryMonoBehaviourAppHangTests.cs index a96edefaf..e8690ce88 100644 --- a/test/Sentry.Unity.Tests/SentryMonoBehaviourAppHangTests.cs +++ b/test/Sentry.Unity.Tests/SentryMonoBehaviourAppHangTests.cs @@ -58,4 +58,20 @@ public IEnumerator StartAppHangHeartbeat_StopsWhenObjectDestroyed() Assert.AreEqual(countAfterDestroy, count); } + + [Test] + public void StartAppHangHeartbeat_PausesNativeDetectorWhenApplicationPauses() + { + var sut = GetSut(); + var pauses = 0; + + sut.StartAppHangHeartbeat(() => { }, () => pauses++); + + sut.UpdatePauseStatus(true); + Assert.AreEqual(1, pauses); + + sut.UpdatePauseStatus(false); + sut.UpdatePauseStatus(true); + Assert.AreEqual(2, pauses); + } }