diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8e3d3c11..96ef13050 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,8 @@
### Features
-- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671))
+- Added `EnableAppHangTracking` (default `true`) to enable app hang (ANR) detection through the `sentry-cocoa` SDK on iOS and macOS. When enabled, sentry-cocoa monitors the main thread and replaces the Unity SDK's C# watchdog on these platforms, allowing Sentry to show a stack trace for the hang event ([#2679](https://github.com/getsentry/sentry-unity/pull/2679))
+- Added `AndroidNativeAnrEnabled` (default `true`) to enable ANR detection through the `sentry-java` SDK. The native ANR integration monitors the Android UI thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. This is complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671))
### Dependencies
diff --git a/src/Sentry.Unity.Editor.iOS/NativeOptions.cs b/src/Sentry.Unity.Editor.iOS/NativeOptions.cs
index 6a0752d60..3ad945c29 100644
--- a/src/Sentry.Unity.Editor.iOS/NativeOptions.cs
+++ b/src/Sentry.Unity.Editor.iOS/NativeOptions.cs
@@ -30,7 +30,8 @@ internal static string Generate(SentryUnityOptions options)
@""maxBreadcrumbs"": @{options.MaxBreadcrumbs},
@""maxCacheItems"": @{options.MaxCacheItems},
@""enableAutoSessionTracking"": @NO,
- @""enableAppHangTracking"": @NO,
+ @""enableAppHangTracking"": @{ToObjCString(options.EnableAppHangTracking)},
+ @""appHangTimeoutInterval"": @{options.AnrTimeout.TotalSeconds.ToString(System.Globalization.CultureInfo.InvariantCulture)},
@""enableCaptureFailedRequests"": @{ToObjCString(options.CaptureFailedRequests)},
@""failedRequestStatusCodes"" : @[{failedRequestStatusCodesArray}],
@""sendDefaultPii"" : @{ToObjCString(options.SendDefaultPii)},
diff --git a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs
index 56d0c2e9e..028b4ef72 100644
--- a/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs
+++ b/src/Sentry.Unity.Editor/ConfigurationWindow/AdvancedTab.cs
@@ -39,21 +39,24 @@ internal static void Display(ScriptableSentryUnityOptions options, SentryCliOpti
EditorGUILayout.Space();
{
- options.AnrDetectionEnabled = EditorGUILayout.BeginToggleGroup(
- new GUIContent("ANR Detection", "Whether the SDK should report 'Application Not " +
- "Responding' events."),
+ options.AnrDetectionEnabled = EditorGUILayout.Toggle(
+ new GUIContent("C# Watchdog", "Whether the SDK should run the C# main-thread watchdog " +
+ "to report 'Application Not Responding' events."),
options.AnrDetectionEnabled);
- EditorGUI.indentLevel++;
+
+ options.EnableAppHangTracking = EditorGUILayout.Toggle(
+ new GUIContent("App Hang Tracking",
+ "Enables app hang (ANR) detection via the native SDK. " +
+ "When enabled, the native SDK monitors the main thread and the Unity SDK's C# ANR " +
+ "watchdog is skipped to avoid duplicate reports."),
+ options.EnableAppHangTracking);
options.AnrTimeout = EditorGUILayout.IntField(
- new GUIContent("Timeout [ms]",
+ new GUIContent("App Hang Timeout [ms]",
"The duration in [ms] for how long the game has to be unresponsive " +
"before an ANR event is reported.\nDefault: 5000ms"),
options.AnrTimeout);
options.AnrTimeout = Math.Max(0, options.AnrTimeout);
-
- EditorGUI.indentLevel--;
- EditorGUILayout.EndToggleGroup();
}
EditorGUILayout.Space();
diff --git a/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs b/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs
index 9d36d8d6a..a3d6c1080 100644
--- a/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs
+++ b/src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs
@@ -66,6 +66,12 @@ public static bool Init(SentryUnityOptions options)
// See https://github.com/getsentry/sentry-unity/issues/1658
OptionsSetInt(cOptions, "enableNetworkBreadcrumbs", 0);
+ Logger?.LogDebug("Setting EnableAppHangTracking: {0}", options.EnableAppHangTracking);
+ OptionsSetInt(cOptions, "enableAppHangTracking", options.EnableAppHangTracking ? 1 : 0);
+
+ Logger?.LogDebug("Setting AppHangTimeoutInterval: {0}s", options.AnrTimeout.TotalSeconds);
+ OptionsSetDouble(cOptions, "appHangTimeoutInterval", options.AnrTimeout.TotalSeconds);
+
Logger?.LogDebug("Setting EnableWatchdogTerminationTracking: {0}", options.IosWatchdogTerminationIntegrationEnabled);
OptionsSetInt(cOptions, "enableWatchdogTerminationTracking", options.IosWatchdogTerminationIntegrationEnabled ? 1 : 0);
@@ -103,6 +109,9 @@ public static bool Init(SentryUnityOptions options)
[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetInt")]
private static extern void OptionsSetInt(IntPtr options, string name, int value);
+ [DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsSetDouble")]
+ private static extern void OptionsSetDouble(IntPtr options, string name, double value);
+
[DllImport("__Internal", EntryPoint = "SentryNativeBridgeOptionsAddFailedRequestStatusCodeRange")]
private static extern void OptionsAddFailedRequestStatusCodeRange(IntPtr options, int min, int max);
diff --git a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs
index 89aa52f04..98d29de0c 100644
--- a/src/Sentry.Unity.iOS/SentryNativeCocoa.cs
+++ b/src/Sentry.Unity.iOS/SentryNativeCocoa.cs
@@ -54,6 +54,12 @@ internal static void Configure(SentryUnityOptions options, RuntimePlatform platf
options.ScopeObserver = new NativeScopeObserver("macOS", options);
}
+ if (options.EnableAppHangTracking)
+ {
+ Logger?.LogDebug("Disabling the C# ANR watchdog - sentry-cocoa handles app hang detection.");
+ options.DisableAnrIntegration();
+ }
+
SentryCocoaBridgeProxy.SetSdkName(); // Since we're not building the SDK we have to overwrite the name here
options.NativeContextWriter = new NativeContextWriter();
diff --git a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs
index e357b6724..5bb07e44f 100644
--- a/src/Sentry.Unity/ScriptableSentryUnityOptions.cs
+++ b/src/Sentry.Unity/ScriptableSentryUnityOptions.cs
@@ -108,6 +108,7 @@ public static string GetConfigPath(string? notDefaultConfigName = null)
[field: SerializeField] public bool AnrDetectionEnabled { get; set; } = true;
[field: SerializeField] public int AnrTimeout { get; set; } = (int)TimeSpan.FromSeconds(5).TotalMilliseconds;
+ [field: SerializeField] public bool EnableAppHangTracking { get; set; } = true;
[field: SerializeField] public bool CaptureFailedRequests { get; set; } = true;
@@ -201,6 +202,7 @@ internal SentryUnityOptions ToSentryUnityOptions(
DiagnosticLevel = DiagnosticLevel,
CaptureLogErrorEvents = CaptureLogErrorEvents,
AnrTimeout = TimeSpan.FromMilliseconds(AnrTimeout),
+ EnableAppHangTracking = EnableAppHangTracking,
CaptureFailedRequests = CaptureFailedRequests,
FilterBadGatewayExceptions = FilterBadGatewayExceptions,
IosNativeSupportEnabled = IosNativeSupportEnabled,
diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs
index 20da43cb0..23870254c 100644
--- a/src/Sentry.Unity/SentryUnityOptions.cs
+++ b/src/Sentry.Unity/SentryUnityOptions.cs
@@ -185,6 +185,18 @@ public sealed class SentryUnityOptions : SentryOptions
///
public bool IosWatchdogTerminationIntegrationEnabled { get; set; } = false;
+ ///
+ /// Enables app hang (ANR) detection on iOS and macOS through the native (sentry-cocoa) SDK.
+ /// When enabled, sentry-cocoa monitors the main thread for hangs and the Unity SDK's
+ /// C# ANR watchdog is skipped to avoid duplicate reports.
+ ///
+ ///
+ /// sentry-cocoa observes the platform run loop directly, which yields more accurate
+ /// app-hang detection than the Unity-side watchdog. Disable this only if you want
+ /// to fall back to the C# watchdog.
+ ///
+ public bool EnableAppHangTracking { get; set; } = true;
+
///
/// Whether the SDK should initialize the native SDK before the game starts. This bakes the options at build-time into
/// the generated Xcode project. Modifying the options at runtime will not affect the options used to initialize
diff --git a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs
index 71aecac29..87ad6e0c7 100644
--- a/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs
+++ b/test/Sentry.Unity.Editor.iOS.Tests/NativeOptionsTests.cs
@@ -58,6 +58,46 @@ public void CreateOptionsFile_NewSentryOptions_ContainsSdkNameSetting()
File.Delete(testOptionsFileName);
}
+ [Test]
+ public void CreateOptionsFile_EnableAppHangTracking_SetsYes()
+ {
+ const string testOptionsFileName = "testOptions.m";
+
+ NativeOptions.CreateFile(testOptionsFileName, new SentryUnityOptions { EnableAppHangTracking = true });
+
+ var nativeOptions = File.ReadAllText(testOptionsFileName);
+ StringAssert.Contains("@\"enableAppHangTracking\": @YES", nativeOptions);
+
+ File.Delete(testOptionsFileName);
+ }
+
+ [Test]
+ public void CreateOptionsFile_AppHangTrackingDisabled_SetsNo()
+ {
+ const string testOptionsFileName = "testOptions.m";
+
+ NativeOptions.CreateFile(testOptionsFileName, new SentryUnityOptions { EnableAppHangTracking = false });
+
+ var nativeOptions = File.ReadAllText(testOptionsFileName);
+ StringAssert.Contains("@\"enableAppHangTracking\": @NO", nativeOptions);
+
+ File.Delete(testOptionsFileName);
+ }
+
+ [Test]
+ public void CreateOptionsFile_AnrTimeout_WrittenAsSeconds()
+ {
+ const string testOptionsFileName = "testOptions.m";
+
+ NativeOptions.CreateFile(testOptionsFileName,
+ new SentryUnityOptions { AnrTimeout = System.TimeSpan.FromMilliseconds(7500) });
+
+ var nativeOptions = File.ReadAllText(testOptionsFileName);
+ StringAssert.Contains("@\"appHangTimeoutInterval\": @7.5", nativeOptions);
+
+ File.Delete(testOptionsFileName);
+ }
+
[Test]
public void CreateOptionsFile_FilterBadGatewayEnabled_AddsFiltering()
{