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

fix: Android 5 attachment issues #1652

Merged
merged 24 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
972e710
attachment issues
bitsandfoxes May 14, 2024
8c4eba9
jni executor for detaching
bitsandfoxes May 14, 2024
8c81872
.
bitsandfoxes May 15, 2024
f2c3fa1
Updated CHANGELOG.md
bitsandfoxes May 15, 2024
d579725
Merge branch 'main' into fix/android-no-bueno
bitsandfoxes May 15, 2024
fbeec2f
added reusable background worker as fallback for old android
bitsandfoxes May 16, 2024
2a67c2d
Merge branch 'fix/android-no-bueno' of https://github.com/getsentry/s…
bitsandfoxes May 16, 2024
295be7b
Format code
getsentry-bot May 16, 2024
5470ec0
.
bitsandfoxes May 16, 2024
b00e3ac
Merge branch 'fix/android-no-bueno' of https://github.com/getsentry/s…
bitsandfoxes May 16, 2024
46dca64
fixed platform dependency and tests
bitsandfoxes May 17, 2024
1f922c1
properly detach
bitsandfoxes May 17, 2024
31b7f7e
comment
bitsandfoxes May 17, 2024
bc09463
Format code
getsentry-bot May 17, 2024
8b7fbf5
yeet complexity
bitsandfoxes May 17, 2024
323b144
merge
bitsandfoxes May 17, 2024
7be3763
don't touch .NET
bitsandfoxes May 17, 2024
919a2cb
Format code
getsentry-bot May 17, 2024
c492499
worker cleanup
bitsandfoxes May 17, 2024
32f6ae3
Create the JNI Executor only on a need to basis
bitsandfoxes May 17, 2024
deeff11
Create the JNI executor only on a need-to basis
bitsandfoxes May 17, 2024
e14bdbc
Update src/Sentry.Unity.Android/SentryNativeAndroid.cs
bitsandfoxes May 17, 2024
ce78387
Update src/Sentry.Unity.Android/SentryNativeAndroid.cs
bitsandfoxes May 17, 2024
67aaa6b
Format code
getsentry-bot May 17, 2024
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Fixes

- The SDK no longer crashes on Android versions 5 and 6 with native support enabled ([#1652](https://github.com/getsentry/sentry-unity/pull/1652))

### Dependencies

- Bump Cocoa SDK from v8.25.2 to v8.26.0 ([#1648](https://github.com/getsentry/sentry-unity/pull/1648))
Expand Down
92 changes: 54 additions & 38 deletions src/Sentry.Unity.Android/AndroidJavaScopeObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,69 +8,85 @@ namespace Sentry.Unity.Android
/// <see href="https://github.com/getsentry/sentry-java"/>
public class AndroidJavaScopeObserver : ScopeObserver
{
public AndroidJavaScopeObserver(SentryOptions options) : base("Android", options) { }
private readonly JniExecutor _jniExecutor;

public AndroidJavaScopeObserver(SentryOptions options, JniExecutor jniExecutor) : base("Android", options)
{
_jniExecutor = jniExecutor;
}

private AndroidJavaObject GetSentryJava() => new AndroidJavaClass("io.sentry.Sentry");

public override void AddBreadcrumbImpl(Breadcrumb breadcrumb)
{
AndroidJNI.AttachCurrentThread();
using var sentry = GetSentryJava();
using var javaBreadcrumb = new AndroidJavaObject("io.sentry.Breadcrumb");
javaBreadcrumb.Set("message", breadcrumb.Message);
javaBreadcrumb.Set("type", breadcrumb.Type);
javaBreadcrumb.Set("category", breadcrumb.Category);
using var javaLevel = breadcrumb.Level.ToJavaSentryLevel();
javaBreadcrumb.Set("level", javaLevel);
sentry.CallStatic("addBreadcrumb", javaBreadcrumb, null);
_jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
using var javaBreadcrumb = new AndroidJavaObject("io.sentry.Breadcrumb");
javaBreadcrumb.Set("message", breadcrumb.Message);
javaBreadcrumb.Set("type", breadcrumb.Type);
javaBreadcrumb.Set("category", breadcrumb.Category);
using var javaLevel = breadcrumb.Level.ToJavaSentryLevel();
javaBreadcrumb.Set("level", javaLevel);
sentry.CallStatic("addBreadcrumb", javaBreadcrumb, null);
});
}

public override void SetExtraImpl(string key, string? value)
{
AndroidJNI.AttachCurrentThread();
using var sentry = GetSentryJava();
sentry.CallStatic("setExtra", key, value);
_jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
sentry.CallStatic("setExtra", key, value);
});
}
public override void SetTagImpl(string key, string value)
{
AndroidJNI.AttachCurrentThread();
using var sentry = GetSentryJava();
sentry.CallStatic("setTag", key, value);
_jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
sentry.CallStatic("setTag", key, value);
});
}

public override void UnsetTagImpl(string key)
{
AndroidJNI.AttachCurrentThread();
using var sentry = GetSentryJava();
sentry.CallStatic("removeTag", key);
_jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
sentry.CallStatic("removeTag", key);
});
}

public override void SetUserImpl(SentryUser user)
{
AndroidJNI.AttachCurrentThread();

AndroidJavaObject? javaUser = null;
try
_jniExecutor.Run(() =>
{
javaUser = new AndroidJavaObject("io.sentry.protocol.User");
javaUser.Set("email", user.Email);
javaUser.Set("id", user.Id);
javaUser.Set("username", user.Username);
javaUser.Set("ipAddress", user.IpAddress);
using var sentry = GetSentryJava();
sentry.CallStatic("setUser", javaUser);
}
finally
{
javaUser?.Dispose();
}
AndroidJavaObject? javaUser = null;
try
{
javaUser = new AndroidJavaObject("io.sentry.protocol.User");
javaUser.Set("email", user.Email);
javaUser.Set("id", user.Id);
javaUser.Set("username", user.Username);
javaUser.Set("ipAddress", user.IpAddress);
using var sentry = GetSentryJava();
sentry.CallStatic("setUser", javaUser);
}
finally
{
javaUser?.Dispose();
}
});
}

public override void UnsetUserImpl()
{
AndroidJNI.AttachCurrentThread();
using var sentry = GetSentryJava();
sentry.CallStatic("setUser", null);
_jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
sentry.CallStatic("setUser", null);
});
}
}
}
116 changes: 116 additions & 0 deletions src/Sentry.Unity.Android/JniExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace Sentry.Unity.Android
{
public class JniExecutor
{
private readonly CancellationTokenSource _shutdownSource;
private readonly AutoResetEvent _taskEvent;
private Delegate _currentTask = null!; // The current task will always be set together with the task event

private TaskCompletionSource<object?>? _taskCompletionSource;

public JniExecutor()
{
_taskEvent = new AutoResetEvent(false);
_shutdownSource = new CancellationTokenSource();

new Thread(DoWork) { IsBackground = true }.Start();
}

private void DoWork()
{
AndroidJNI.AttachCurrentThread();

var waitHandles = new[] { _taskEvent, _shutdownSource.Token.WaitHandle };

while (true)
{
var index = WaitHandle.WaitAny(waitHandles);
if (index > 0)
{
// We only care about the _taskEvent
break;
}

try
{
// Matching the type of the `_currentTask` exactly. The result gets cast to the expected type
// when returning from the blocking call.
switch (_currentTask)
{
case Action action:
{
action.Invoke();
_taskCompletionSource?.SetResult(null);
break;
}
case Func<bool?> func1:
{
var result = func1.Invoke();
_taskCompletionSource?.SetResult(result);
break;
}
case Func<string?> func2:
{
var result = func2.Invoke();
_taskCompletionSource?.SetResult(result);
break;
}
default:
throw new ArgumentException("Invalid type for _currentTask.");
}
}
catch (Exception e)
{
_taskCompletionSource?.SetException(e);
}
}

AndroidJNI.DetachCurrentThread();
}

public TResult? Run<TResult>(Func<TResult?> jniOperation)
{
_taskCompletionSource = new TaskCompletionSource<object?>();
_currentTask = jniOperation;
_taskEvent.Set();

try
{
return (TResult?)_taskCompletionSource.Task.GetAwaiter().GetResult();
}
catch (Exception e)
{
Debug.unityLogger.Log(LogType.Exception, UnityLogger.LogTag, $"Error during JNI execution: {e}");
}

return default;
}

public void Run(Action jniOperation)
{
_taskCompletionSource = new TaskCompletionSource<object?>();
_currentTask = jniOperation;
_taskEvent.Set();

try
{
_taskCompletionSource.Task.Wait();
}
catch (Exception e)
{
Debug.unityLogger.Log(LogType.Exception, UnityLogger.LogTag, $"Error during JNI execution: {e}");
}
}

public void Dispose()
{
_shutdownSource.Cancel();
_taskEvent.Dispose();
}
}
}
8 changes: 8 additions & 0 deletions src/Sentry.Unity.Android/NativeContextWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ namespace Sentry.Unity.Android
{
internal class NativeContextWriter : ContextWriter
{
private readonly JniExecutor _jniExecutor;

public NativeContextWriter(JniExecutor jniExecutor)
{
_jniExecutor = jniExecutor;
}

protected override void WriteScope(
string? AppStartTime,
string? AppBuildType,
Expand Down Expand Up @@ -44,6 +51,7 @@ protected override void WriteScope(
// the "unity" context, but it doesn't seem so useful and the effort to do is larger because there's no
// class for it in Java - not sure how we could add a generic context object in Java...
SentryJava.WriteScope(
_jniExecutor,
GpuId,
GpuName,
GpuVendorName,
Expand Down
46 changes: 22 additions & 24 deletions src/Sentry.Unity.Android/SentryJava.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading;
using UnityEngine;

namespace Sentry.Unity.Android
Expand All @@ -13,17 +14,15 @@ namespace Sentry.Unity.Android
/// <see href="https://github.com/getsentry/sentry-java"/>
internal static class SentryJava
{
internal static string? GetInstallationId()
internal static string? GetInstallationId(JniExecutor jniExecutor)
{
if (!Attach())
return jniExecutor.Run(() =>
{
return null;
}

using var sentry = GetSentryJava();
using var hub = sentry.CallStatic<AndroidJavaObject>("getCurrentHub");
using var options = hub?.Call<AndroidJavaObject>("getOptions");
return options?.Call<string>("getDistinctId");
using var sentry = GetSentryJava();
using var hub = sentry.CallStatic<AndroidJavaObject>("getCurrentHub");
using var options = hub?.Call<AndroidJavaObject>("getOptions");
return options?.Call<string>("getDistinctId");
});
}

/// <summary>
Expand All @@ -36,30 +35,29 @@ internal static class SentryJava
/// True if the last run terminated in a crash. No otherwise.
/// If the SDK wasn't able to find this information, null is returned.
/// </returns>
public static bool? CrashedLastRun()
public static bool? CrashedLastRun(JniExecutor jniExecutor)
{
if (!Attach())
return jniExecutor.Run(() =>
{
return null;
}
using var sentry = GetSentryJava();
using var jo = sentry.CallStatic<AndroidJavaObject>("isCrashedLastRun");
return jo?.Call<bool>("booleanValue");
using var sentry = GetSentryJava();
using var jo = sentry.CallStatic<AndroidJavaObject>("isCrashedLastRun");
return jo?.Call<bool>("booleanValue");
});
}

public static void Close()
public static void Close(JniExecutor jniExecutor)
{
if (Attach())
jniExecutor.Run(() =>
{
using var sentry = GetSentryJava();
sentry.CallStatic("close");
}
});
}

private static bool Attach() => AndroidJNI.AttachCurrentThread() == 0;
private static AndroidJavaObject GetSentryJava() => new AndroidJavaClass("io.sentry.Sentry");

public static void WriteScope(
JniExecutor jniExecutor,
int? GpuId,
string? GpuName,
string? GpuVendorName,
Expand All @@ -76,17 +74,17 @@ public static void WriteScope(
bool? GpuMultiThreadedRendering,
string? GpuGraphicsShaderLevel)
{
if (Attach())
jniExecutor.Run(() =>
{
using var gpu = new AndroidJavaObject("io.sentry.protocol.Gpu");
gpu.SetIfNotNull("name", GpuName);
gpu.SetIfNotNull("id", GpuId);
int intVendorId;
if (GpuVendorId is not null && int.TryParse(GpuVendorId, out intVendorId) && intVendorId != 0)
if (GpuVendorId is not null && int.TryParse(GpuVendorId, out var intVendorId) && intVendorId != 0)
{
using var integer = new AndroidJavaObject("java.lang.Integer", intVendorId);
gpu.Set("vendorId", integer);
}

gpu.SetIfNotNull("vendorName", GpuVendorName);
gpu.SetIfNotNull("memorySize", GpuMemorySize);
gpu.SetIfNotNull("apiType", GpuApiType);
Expand All @@ -100,7 +98,7 @@ public static void WriteScope(
using var contexts = scope.Call<AndroidJavaObject>("getContexts");
contexts.Call("setGpu", gpu);
}));
}
});
}

private static void SetIfNotNull<T>(this AndroidJavaObject javaObject, string property, T? value, string? valueClass = null)
Expand Down
Loading