diff --git a/integration-test/android.Tests.ps1 b/integration-test/android.Tests.ps1 index 6dcb26776d..60f16293d3 100644 --- a/integration-test/android.Tests.ps1 +++ b/integration-test/android.Tests.ps1 @@ -177,13 +177,8 @@ Describe 'MAUI app (, )' -ForEach $cases -Skip:(- Dump-ServerErrors -Result $result $result.HasErrors() | Should -BeFalse $result.Events() | Should -AnyElementMatch "`"type`":`"System.NullReferenceException`"" - # TODO: fix redundant SIGSEGV in Release (#3954) - if ($configuration -eq "Release") { - { $result.Events() | Should -Not -AnyElementMatch "`"type`":`"SIGSEGV`"" } | Should -Throw - } else { - $result.Events() | Should -Not -AnyElementMatch "`"type`":`"SIGSEGV`"" - $result.Events() | Should -HaveCount 1 - } + $result.Events() | Should -Not -AnyElementMatch "`"type`":`"SIGSEGV`"" + $result.Events() | Should -HaveCount 1 } It 'Delivers battery breadcrumbs in main thread ()' { diff --git a/integration-test/net9-maui/MauiProgram.cs b/integration-test/net9-maui/MauiProgram.cs index e1c619cdb5..ceb70bd043 100644 --- a/integration-test/net9-maui/MauiProgram.cs +++ b/integration-test/net9-maui/MauiProgram.cs @@ -14,6 +14,7 @@ public static MauiApp CreateMauiApp() { #if ANDROID options.Dsn = "{{SENTRY_DSN}}"; + options.Native.ExperimentalOptions.SignalHandlerStrategy = Sentry.Android.SignalHandlerStrategy.ChainAtStart; #endif options.Debug = false; options.DiagnosticLevel = SentryLevel.Error; diff --git a/src/Sentry/Platforms/Android/BindableNativeSentryOptions.cs b/src/Sentry/Platforms/Android/BindableNativeSentryOptions.cs index f573c1e817..e8ce8b4444 100644 --- a/src/Sentry/Platforms/Android/BindableNativeSentryOptions.cs +++ b/src/Sentry/Platforms/Android/BindableNativeSentryOptions.cs @@ -1,3 +1,5 @@ +using Sentry.Android; + // ReSharper disable once CheckNamespace namespace Sentry; @@ -40,6 +42,7 @@ public class NativeOptions internal class NativeExperimentalOptions { public NativeSentryReplayOptions SessionReplay { get; set; } = new(); + public SignalHandlerStrategy? SignalHandlerStrategy { get; set; } } internal class NativeSentryReplayOptions @@ -91,6 +94,10 @@ public void ApplyTo(SentryOptions.NativeOptions options) } ExperimentalOptions.SessionReplay.RedactAllText = options.ExperimentalOptions.SessionReplay.MaskAllText; ExperimentalOptions.SessionReplay.RedactAllImages = options.ExperimentalOptions.SessionReplay.MaskAllImages; + if (ExperimentalOptions.SignalHandlerStrategy is { } signalHandlerStrategy) + { + options.ExperimentalOptions.SignalHandlerStrategy = signalHandlerStrategy; + } } } } diff --git a/src/Sentry/Platforms/Android/NativeOptions.cs b/src/Sentry/Platforms/Android/NativeOptions.cs index ea50ff1f81..eacad17cb9 100644 --- a/src/Sentry/Platforms/Android/NativeOptions.cs +++ b/src/Sentry/Platforms/Android/NativeOptions.cs @@ -1,3 +1,5 @@ +using Sentry.Android; + // ReSharper disable once CheckNamespace namespace Sentry; @@ -264,6 +266,20 @@ public void AddInAppInclude(string prefix) public class NativeExperimentalOptions { public NativeSentryReplayOptions SessionReplay { get; set; } = new(); + + /// + /// Gets or sets the strategy for how Sentry Native's signal handler interacts with the CLR/Mono + /// signal handler. + /// The default value is . + /// + /// + /// .NET runtimes 10.0.0–10.0.3 (.NET SDKs 10.0.100–10.0.103) are not compatible with + /// . On affected versions, + /// the SDK automatically falls back to . + /// The issue was resolved in .NET runtime 10.0.4 (.NET SDK 10.0.200). See + /// dotnet/runtime#123346. + /// + public SignalHandlerStrategy SignalHandlerStrategy { get; set; } = SignalHandlerStrategy.Default; } public class NativeSentryReplayOptions diff --git a/src/Sentry/Platforms/Android/SentrySdk.cs b/src/Sentry/Platforms/Android/SentrySdk.cs index 919ec00357..7b3971287f 100644 --- a/src/Sentry/Platforms/Android/SentrySdk.cs +++ b/src/Sentry/Platforms/Android/SentrySdk.cs @@ -64,7 +64,22 @@ private static void InitSentryAndroidSdk(SentryOptions options) o.ServerName = options.ServerName; o.SessionTrackingIntervalMillis = (long)options.AutoSessionTrackingInterval.TotalMilliseconds; o.ShutdownTimeoutMillis = (long)options.ShutdownTimeout.TotalMilliseconds; - o.SetNativeHandlerStrategy(JavaSdk.Android.Core.NdkHandlerStrategy.SentryHandlerStrategyDefault); + + var signalHandlerStrategy = options.Native.ExperimentalOptions.SignalHandlerStrategy; + if (signalHandlerStrategy == SignalHandlerStrategy.ChainAtStart + && System.Environment.Version is { Major: 10, Minor: 0, Build: < 4 }) + { + options.LogWarning( + "SignalHandlerStrategy.ChainAtStart is not compatible with .NET runtime {0}. " + + "Falling back to SignalHandlerStrategy.Default. Update to .NET runtime 10.0.4 or later.", + System.Environment.Version); + signalHandlerStrategy = SignalHandlerStrategy.Default; + } + o.SetNativeHandlerStrategy(signalHandlerStrategy switch + { + SignalHandlerStrategy.ChainAtStart => NdkHandlerStrategy.SentryHandlerStrategyChainAtStart, + _ => NdkHandlerStrategy.SentryHandlerStrategyDefault + }); if (options.CacheDirectoryPath is { } cacheDirectoryPath) { diff --git a/src/Sentry/Platforms/Android/SignalHandlerStrategy.cs b/src/Sentry/Platforms/Android/SignalHandlerStrategy.cs new file mode 100644 index 0000000000..642d4d1c79 --- /dev/null +++ b/src/Sentry/Platforms/Android/SignalHandlerStrategy.cs @@ -0,0 +1,31 @@ +namespace Sentry.Android; + +/// +/// Defines how Sentry Native's signal handler interacts with the CLR/Mono +/// signal handler. +/// +public enum SignalHandlerStrategy +{ + /// + /// Sentry Native captures the crash first, then invokes the .NET runtime's signal + /// handler. The runtime may convert the same signal into a managed exception (e.g., + /// SIGSEGV into NullReferenceException), which can result in duplicate + /// crash reports. + /// + Default, + /// + /// Sentry Native invokes the .NET runtime's signal handler first, then captures the + /// native crash. This avoids duplicate crash reports from both the native signal and + /// the managed exception. This strategy is supported on Android 8.0 (API level 26) + /// and later; on older versions, Sentry Native silently falls back to + /// . + /// + /// + /// .NET runtimes 10.0.0–10.0.3 (.NET SDKs 10.0.100–10.0.103) are not compatible with + /// this strategy. On affected versions, the SDK automatically falls back to + /// . The issue was resolved in .NET runtime 10.0.4 + /// (.NET SDK 10.0.200). See + /// dotnet/runtime#123346. + /// + ChainAtStart +}