Skip to content

Commit

Permalink
Feat: native last crash (#615)
Browse files Browse the repository at this point in the history
* feat: native CrashedLastRun()

* test: CrashedLastRun()

* fix: iOS CrashedLastRun()

* update sentry-native to the latest HEAD

* refactor: use native crashed_last_run()

* chore: update changelog

* ci: simple retry logic for the smoke-crash test

* fix: merge-related import duplication

* Apply suggestions from code review

Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>

Co-authored-by: Bruno Garcia <bruno@brunogarcia.com>
  • Loading branch information
vaind and bruno-garcia authored Mar 10, 2022
1 parent 9318a96 commit 0b27b31
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 43 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

- iOS builds no longer break when native support disabled or not available ([#592](https://github.com/getsentry/sentry-unity/pull/592))
- Close sentry instance when quitting the app ([#608](https://github.com/getsentry/sentry-unity/pull/608))
- iOS options.CrashedLastRun() reported an incorrect value ([#615](https://github.com/getsentry/sentry-unity/pull/615))

### Features

- Update native SDK to v0.4.16 (pre-release) ([#615](https://github.com/getsentry/sentry-unity/pull/615))
- Windows native support (64-bit)
- native crash handler ([#380](https://github.com/getsentry/sentry-unity/pull/380))
- configuration & log forwarding ([#577](https://github.com/getsentry/sentry-unity/pull/577))
Expand Down
2 changes: 1 addition & 1 deletion modules/sentry-native
Submodule sentry-native updated 83 files
+5 −1 .github/workflows/ci.yml
+1 −0 .gitignore
+65 −0 CHANGELOG.md
+55 −5 CMakeLists.txt
+11 −3 CONTRIBUTING.md
+10 −3 Makefile
+14 −0 README.md
+50 −0 examples/example.c
+2 −2 external/CMakeLists.txt
+1 −1 external/breakpad
+1 −1 external/crashpad
+560 −15 include/sentry.h
+11 −0 src/CMakeLists.txt
+2 −0 src/backends/sentry_backend_inproc.c
+109 −0 src/modulefinder/sentry_modulefinder_aix.c
+103 −17 src/modulefinder/sentry_modulefinder_linux.c
+6 −0 src/modulefinder/sentry_modulefinder_linux.h
+29 −3 src/path/sentry_path_unix.c
+412 −24 src/sentry_core.c
+37 −4 src/sentry_core.h
+42 −1 src/sentry_database.c
+10 −0 src/sentry_database.h
+62 −1 src/sentry_envelope.c
+6 −0 src/sentry_envelope.h
+44 −0 src/sentry_json.c
+58 −1 src/sentry_options.c
+4 −0 src/sentry_options.h
+67 −14 src/sentry_os.c
+8 −0 src/sentry_ratelimiter.c
+6 −0 src/sentry_ratelimiter.h
+74 −7 src/sentry_scope.c
+16 −0 src/sentry_scope.h
+10 −6 src/sentry_session.c
+0 −61 src/sentry_string.c
+56 −18 src/sentry_string.h
+76 −1 src/sentry_sync.c
+22 −0 src/sentry_sync.h
+520 −0 src/sentry_tracing.c
+51 −0 src/sentry_tracing.h
+19 −0 src/sentry_transport.c
+7 −0 src/sentry_transport.h
+27 −2 src/sentry_utils.c
+22 −0 src/sentry_uuid.c
+13 −3 src/sentry_uuid.h
+60 −0 src/sentry_value.c
+33 −0 src/sentry_value.h
+189 −0 src/symbolizer/sentry_symbolizer_unix.c
+13 −0 src/transports/sentry_transport_curl.c
+18 −0 src/transports/sentry_transport_winhttp.c
+8 −2 src/unwinder/sentry_unwinder_libbacktrace.c
+48 −2 tests/__init__.py
+36 −19 tests/assertions.py
+7 −2 tests/conditions.py
+1 −1 tests/test_build_static.py
+60 −1 tests/test_integration_http.py
+12 −0 tests/test_integration_ratelimits.py
+22 −0 tests/test_integration_stdout.py
+2 −0 tests/unit/CMakeLists.txt
+0 −2 tests/unit/fuzz.c
+0 −1 tests/unit/test_attachments.c
+69 −1 tests/unit/test_basic.c
+28 −1 tests/unit/test_concurrency.c
+0 −1 tests/unit/test_consent.c
+32 −1 tests/unit/test_envelopes.c
+0 −1 tests/unit/test_failures.c
+1 −1 tests/unit/test_fuzzfailures.c
+0 −1 tests/unit/test_logger.c
+0 −1 tests/unit/test_modulefinder.c
+0 −1 tests/unit/test_mpack.c
+0 −1 tests/unit/test_path.c
+0 −1 tests/unit/test_ratelimiter.c
+37 −0 tests/unit/test_sampling.c
+0 −1 tests/unit/test_session.c
+0 −1 tests/unit/test_slice.c
+12 −1 tests/unit/test_symbolizer.c
+20 −0 tests/unit/test_sync.c
+811 −0 tests/unit/test_tracing.c
+0 −2 tests/unit/test_uninit.c
+11 −2 tests/unit/test_unwinder.c
+0 −1 tests/unit/test_utils.c
+22 −2 tests/unit/test_uuid.c
+56 −1 tests/unit/test_value.c
+24 −0 tests/unit/tests.inc
4 changes: 2 additions & 2 deletions package-dev/Plugins/iOS/SentryNativeBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

NS_ASSUME_NONNULL_BEGIN

bool CrashedLastRun() {
return[SentrySDK crashedLastRun];
int CrashedLastRun() {
return [SentrySDK crashedLastRun] ? 1 : 0;
}

void Close() {
Expand Down
19 changes: 18 additions & 1 deletion samples/unity-of-bugs/Assets/Scripts/SmokeTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public void Start()
{
SmokeTestCrash();
}
else if (arg == "post-crash")
{
PostCrashTest();
}
else
{
Debug.Log($"Unknown command line argument: {arg}");
Expand Down Expand Up @@ -127,7 +131,10 @@ public static void SmokeTest()
{
Debug.Log("SMOKE TEST: Start");

InitSentry(new SentryUnityOptions() { CreateHttpClientHandler = () => t });
var options = new SentryUnityOptions() { CreateHttpClientHandler = () => t };
InitSentry(options);

t.Expect("options.CrashedLastRun() == false", !options.CrashedLastRun());

var currentMessage = 0;
t.ExpectMessage(currentMessage, "'type':'session'");
Expand Down Expand Up @@ -196,6 +203,16 @@ public static void SmokeTestCrash()
Application.Quit(-1);
}

public static void PostCrashTest()
{
var options = new SentryUnityOptions();
InitSentry(options);

var t = new TestHandler();
t.Expect("options.CrashedLastRun() == true", options.CrashedLastRun());
t.Pass();
}

private static void AddContext()
{
SentrySdk.AddBreadcrumb("crumb", "bread", "error", new Dictionary<string, string>() { { "foo", "bar" } }, BreadcrumbLevel.Critical);
Expand Down
41 changes: 24 additions & 17 deletions src/Sentry.Unity.Native/SentryNative.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Sentry.Extensibility;
using Sentry.Unity.Integrations;
using System.Collections.Generic;

namespace Sentry.Unity.Native
{
Expand All @@ -8,6 +9,8 @@ namespace Sentry.Unity.Native
/// </summary>
public static class SentryNative
{
private static Dictionary<string, bool> _perDirectoryCrashInfo = new Dictionary<string, bool>();

/// <summary>
/// Configures the native SDK.
/// </summary>
Expand All @@ -24,24 +27,28 @@ public static void Configure(SentryUnityOptions options)
};
options.ScopeObserver = new NativeScopeObserver(options);
options.EnableScopeSync = true;
// options.CrashedLastRun = () =>
// {
// var crashedLastRun = SentryJava.CrashedLastRun();
// if (crashedLastRun is null)
// {
// // Could happen if the Android SDK wasn't initialized before the .NET layer.
// options.DiagnosticLogger?
// .LogWarning("Unclear from the native SDK if the previous run was a crash. Assuming it was not.");
// crashedLastRun = false;
// }
// else
// {
// options.DiagnosticLogger?
// .LogDebug("Native Android SDK reported: 'crashedLastRun': '{0}'", crashedLastRun);
// }

// return crashedLastRun.Value;
// };
// Note: we must actually call the function now and on every other call use the value we get here.
// Additionally, we cannot call this multiple times for the same directory, because the result changes
// on subsequent runs. Therefore, we cache the value during the whole runtime of the application.
var cacheDirectory = SentryNativeBridge.GetCacheDirectory(options);
bool crashedLastRun = false;
// In the event the SDK is re-initialized with a different path on disk, we need to track which ones were already read
// Similarly we need to cache the value of each call since a subsequent call would return a different value
// as the file used on disk to mark it as crashed is deleted after we read it.
lock (_perDirectoryCrashInfo)
{
if (!_perDirectoryCrashInfo.TryGetValue(cacheDirectory, out crashedLastRun))
{
crashedLastRun = SentryNativeBridge.HandleCrashedLastRun(options);
_perDirectoryCrashInfo.Add(cacheDirectory, crashedLastRun);

options.DiagnosticLogger?
.LogDebug("Native SDK reported: 'crashedLastRun': '{0}'", crashedLastRun);
}
}
options.CrashedLastRun = () => crashedLastRun;

// At this point Unity has taken the signal handler and will not invoke the original handler (Sentry)
// So we register our backend once more to make sure user-defined data is available in the crash report.
SentryNativeBridge.ReinstallBackend();
Expand Down
53 changes: 40 additions & 13 deletions src/Sentry.Unity.Native/SentryNativeBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Runtime.InteropServices;
using Sentry.Extensibility;
using AOT;
using System.Threading;
using System.Text;

namespace Sentry.Unity
Expand All @@ -20,6 +19,9 @@ namespace Sentry.Unity
/// <see href="https://github.com/getsentry/sentry-native"/>
public static class SentryNativeBridge
{

public static bool CrashedLastRun;

public static void Init(SentryUnityOptions options)
{
var cOptions = sentry_options_new();
Expand Down Expand Up @@ -52,19 +54,16 @@ public static void Init(SentryUnityOptions options)
options.DiagnosticLogger?.LogDebug("Disabling native auto session tracking");
sentry_options_set_auto_session_tracking(cOptions, 0);

if (options.CacheDirectoryPath is not null)
var dir = GetCacheDirectory(options);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath on Windows: {0}", dir);
sentry_options_set_database_pathw(cOptions, dir);
}
else
{
var dir = Path.Combine(options.CacheDirectoryPath, "SentryNative");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath on Windows: {0}", dir);
sentry_options_set_database_pathw(cOptions, dir);
}
else
{
options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath: {0}", dir);
sentry_options_set_database_path(cOptions, dir);
}
options.DiagnosticLogger?.LogDebug("Setting CacheDirectoryPath: {0}", dir);
sentry_options_set_database_path(cOptions, dir);
}

if (options.DiagnosticLogger is null)
Expand All @@ -84,6 +83,28 @@ public static void Init(SentryUnityOptions options)

public static void Close() => sentry_close();

/// Call after native init() to check if the application has crashed in the previous run and clear the status.
/// Because the file is removed, the result will change on subsequent calls so it must be cached for the current runtime.
internal static bool HandleCrashedLastRun(SentryUnityOptions options)
{
var result = sentry_get_crashed_last_run() == 1;
sentry_clear_crashed_last_run();
return result;
}

internal static string GetCacheDirectory(SentryUnityOptions options)
{
if (options.CacheDirectoryPath is null)
{
// same as the default of sentry-native
return Path.Combine(Directory.GetCurrentDirectory(), ".sentry-native");
}
else
{
return Path.Combine(options.CacheDirectoryPath, "SentryNative");
}
}

// libsentry.so
[DllImport("sentry")]
private static extern IntPtr sentry_options_new();
Expand Down Expand Up @@ -172,6 +193,12 @@ private static void nativeLog(int cLevel, string message, IntPtr args, IntPtr us
[DllImport("sentry")]
private static extern int sentry_close();

[DllImport("sentry")]
private static extern int sentry_get_crashed_last_run();

[DllImport("sentry")]
private static extern int sentry_clear_crashed_last_run();

/// <summary>
/// Re-installs the sentry-native backend essentially retaking the signal handlers.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Unity.iOS/SentryCocoaBridgeProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Sentry.Unity.iOS
internal static class SentryCocoaBridgeProxy
{
[DllImport("__Internal")]
public static extern bool CrashedLastRun();
public static extern int CrashedLastRun();

[DllImport("__Internal")]
public static extern void Close();
Expand Down
2 changes: 1 addition & 1 deletion src/Sentry.Unity.iOS/SentryNativeIos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static void Configure(SentryUnityOptions options)
options.EnableScopeSync = true;
options.CrashedLastRun = () =>
{
var crashedLastRun = SentryCocoaBridgeProxy.CrashedLastRun();
var crashedLastRun = SentryCocoaBridgeProxy.CrashedLastRun() == 1;
options.DiagnosticLogger?
.LogDebug("Native iOS SDK reported: 'crashedLastRun': '{0}'", crashedLastRun);
Expand Down
14 changes: 7 additions & 7 deletions test/Scripts.Integration.Test/integration-run-smoke-test.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ function RunTest([string] $type) {
Else {
$info = "Test process finished with status code $($process.ExitCode)."
If ($type -ne "smoke-crash") {
if ("$AppDataDir" -ne "") {
Get-Content "$AppDataDir/Player.log"
}
throw $info
}
Write-Host $info
Expand Down Expand Up @@ -125,7 +122,8 @@ if ($Smoke) {

# Native crash test
if ($Crash) {
$runs = 1 # You can increase this to run the crash test multiple times in a loop (until it fails)
# You can increase this to retry multiple times. Seems a bit flaky at the moment in CI.
$runs = 5
for ($run = 1; $run -le $runs; $run++) {
$httpServer = RunApiServer
RunTest "smoke-crash"
Expand All @@ -134,8 +132,8 @@ if ($Crash) {
$successMessage = "POST /api/12345/minidump/"

Write-Host "Waiting for the expected message to appear in the server output logs ..."
# Wait for 1 minute (600 * 100 milliseconds) until the expected message comes in
for ($i = 0; $i -lt 600; $i++) {
# Wait for 30 seconds (300 * 100 milliseconds) until the expected message comes in
for ($i = 0; $i -lt 300; $i++) {
$output = (Get-Content $httpServer.outFile -Raw) + (Get-Content $httpServer.errFile -Raw)
if ("$output".Contains($successMessage)) {
break
Expand Down Expand Up @@ -167,8 +165,10 @@ if ($Crash) {
if ($output.Contains($successMessage)) {
Write-Host "smoke-crash test $run/$runs : PASSED" -ForegroundColor Green
}
else {
elseif ($run -ne $runs) {
Write-Error "smoke-crash test $run/$runs : FAILED"
}
}

RunTest "post-crash"
}

0 comments on commit 0b27b31

Please sign in to comment.