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

[Mono.Android.dll] Fix [SupportedOSPlatform] warnings. #8247

Merged
merged 2 commits into from
Aug 10, 2023

Conversation

jpobst
Copy link
Contributor

@jpobst jpobst commented Aug 7, 2023

Fixes #7590

Fix the remaining [SupportedOSPlatform] warnings in manually bound code. Other issues we were previously seeing appear to have been a bug in earlier .NET 8 previews.

Of particular note, the constructors for Java.Lang.Boolean, Java.Lang.Integer, etc. have been marked as Deprecated in API-33. (source).

This PR replaces them with the suggested valueOf methods.

@jonpryor Please verify this change is acceptable for our use case.

@jpobst jpobst force-pushed the supported-os-warnings branch from ec203a4 to 8c1ffe8 Compare August 8, 2023 16:38
@dotnet dotnet deleted a comment from azure-pipelines bot Aug 9, 2023
@jpobst jpobst marked this pull request as ready for review August 9, 2023 19:27
@jpobst jpobst requested a review from jonpryor as a code owner August 9, 2023 19:27
@@ -170,7 +170,7 @@ public void Put (string key, int value)
id_put_Ljava_lang_String_Ljava_lang_Integer_ = JNIEnv.GetMethodID (class_ref, "put", "(Ljava/lang/String;Ljava/lang/Integer;)V");
IntPtr jkey = JNIEnv.NewString (key);
try {
using (var val = new Java.Lang.Integer (value))
using (var val = Java.Lang.Integer.ValueOf (value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Integer.valueOf() may be the recommended alternative, but…

Our problem is going to be "instance sharing": Integer.valueOf() "yield[s] significantly better space and time performance" because it has a cache:

This presents the possibility of "accidental instance sharing". What happens if two threads run:

var v = Java.Lang.Integer.ValueOf(42);

What happens is that Integer.valueOf(42) will return the same Java-side instance, so Integer.ValueOf() will (attempt) to return the same managed callable wrapper instance.

So far, so fine. But that using is what kills things; if two threads run:

using (var val = Java.Lang.Integer.ValueOf(42)) {
    val.FloatValue();
}

then we have a possible "single world ordering" of:

  1. Thread 1: calls Integer.ValueOf(42), calls Integer.valueOf(42), wrapper instance created and registered.
  2. Thread 1: calls val.FloatValue()
  3. Thread 2: calls Integer.ValueOf(42), gets wrapper instance created in (1)
  4. Thread 1: calls val.Dispose()
  5. Thread 2: calls val.FloatValue().

Step (5) throws an System.ObjectDisposedException. Oops.

You can either have using (var v = new T(…)) or we can have var v = T.valueOf(…). We cannot intermix them, not safely.

(Of course, I have some ideas to make this "more reasonable", but they have not been finished yet.)

We thus have a question: which is "worse":

  1. the extra GREFs that will happen by using Integer.ValueOf() without a using block, or
  2. the extra Java-side (and C# side!) Integer instances which will be created by using the constructor?

That would be a fascinating microbenchmark to see the results of.

For now, I think we should stick with the current using approach and continue using the constructors.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same logic applies to all the other types.

else if (type == typeof (string))
return JNIEnv.NewString ((string)obj);
else if (typeof (IJavaObject).IsAssignableFrom (type))
if (obj is bool bool_obj)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Interestingly", I can't easily find any code which calls JavaObject.GetHandle(object). Maybe we could just remove it entirely?

return ((IJavaObject)obj).Handle;
else
return new JavaObject (obj).Handle;
}

public static object? GetObject (IntPtr handle, JniHandleOwnership transfer)
{
Java.Lang.Object? jlo = Java.Lang.Object.GetObject (handle, transfer) as Java.Lang.Object;
if (jlo == null)
if (Java.Lang.Object.GetObject (handle, transfer) is not Java.Lang.Object jlo)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, what calls JavaObject.GetObject()? git grep JavaObject.GetObject only finds:

src/Mono.Android/Android.Runtime/JNIEnv.cs:                     var javaException = JavaObject.GetObject<Java.Lang.Throwable> (env, javaExceptionPtr, JniHandleOwnership.DoNotTransfer)!;

which is a generic method invocation, and thus can't be this method. Is this method actually used anymore? Can we just remove it?

else if (jlo is Java.Lang.Double)
return Dispose ((Java.Lang.Double) jlo, v => v.DoubleValue ());
else if (jlo is Java.Lang.Boolean bool_jlo)
return Dispose (bool_jlo, v => v.BooleanValue ());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…I mostly care about who's calling this method so I can know about possible value sharing, a'la Integer.valueOf(). Is it safe to call Dispose()? Is this a ticking timebomb or just dead code?

{ typeof (long), value => new Java.Lang.Long ((long) value) },
{ typeof (float), value => new Java.Lang.Float ((float) value) },
{ typeof (double), value => new Java.Lang.Double ((double) value) },
{ typeof (bool), value => Java.Lang.Boolean.ValueOf ((bool) value) },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes are probably safe, in that I can't easily tell if there are any callers that will be Dispose()ing of the value…

@@ -308,39 +308,39 @@ static class JavaConvert {

static Dictionary<Type, Func<object, IntPtr>> LocalJniHandleConverters = new Dictionary<Type, Func<object, IntPtr>> {
{ typeof (bool), value => {
using (var v = new Java.Lang.Boolean ((bool) value))
using (var v = Java.Lang.Boolean.ValueOf ((bool) value))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same accidental value sharing problem…

@jonpryor jonpryor merged commit 0c5b60d into main Aug 10, 2023
@jonpryor jonpryor deleted the supported-os-warnings branch August 10, 2023 19:32
grendello added a commit to grendello/xamarin-android that referenced this pull request Aug 10, 2023
* main:
  [Mono.Android.dll] Fix [SupportedOSPlatform] warnings. (dotnet#8247)
  Bump to xamarin/monodroid@a5248591 (dotnet#8258)
  [Mono.Android] Bind `java.time` and `java.time.chrono` packages. (dotnet#8088)
  Bump to xamarin/Java.Interop/main@5adb4d4 (dotnet#8251)
  [ci] Use fewer agents for macOS MSBuild tests (dotnet#8257)
  [ci] Report when dotnet-test-slicer fails (dotnet#8259)
  [monodroid] Do not build host or classic runtimes (dotnet#8256)
@github-actions github-actions bot locked and limited conversation to collaborators Jan 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Fix [SupportedOSPlatform] warnings in Mono.Android.dll
2 participants