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

Feat: native last crash #615

Merged
merged 9 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from 8 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
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
38 changes: 21 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>();
vaind marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Configures the native SDK.
/// </summary>
Expand All @@ -24,24 +27,25 @@ 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;
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;
vaind marked this conversation as resolved.
Show resolved Hide resolved

// 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"
}