-
Notifications
You must be signed in to change notification settings - Fork 53
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
Type coercion and JavaCast<T> support #10
Labels
enhancement
Proposed change to current functionality
java-interop
Runtime bridge between .NET and Java
Comments
jonpryor
added a commit
that referenced
this issue
Dec 7, 2015
Context: https://bugzilla.xamarin.com/show_bug.cgi?id=25443 Fixes: #2 Fixes: #3 The problem with JniRuntime.JniValueManager.GetObject() is that it was constrained to only return IJavaPeerable instances. This constraint in turn necessitated the introduction of JniMarshal.GetValue<T>() (commit 1e14a61) so that "anything" could be stored within a JavaObjectArray<T>, proxying values if required. This in turn led to a dichotomy: should JniRuntime.GetObject<T>() be used, or should JniMarshal.GetValue<T>() be used? This dichotomy was partially resolved in commit 77a6bf8, which removed JniMarshal.GetValue<T>() and added a JniRuntime.JniValueManager.GetValue<T>() method, but that just introduced a different dichotomy: should JniValueManager.GetObject() be used or JniValueManager.GetValue()? Simplify this mess: kill JniValueManager.GetObject() (and all overloads), and instead rely on JniValueManager.GetValue(). Additionally, add JniValueManager.CreateValue() overloads (Issue #3) and fix JniValueMarshaler<T>.CreateGenericValue() logic to *not* lookup registered values, as per commit 77a6bf8: > clean[] up JniValueMarshaler.Create*Value() methods to always > create values when able, simplifying the logic of > JniValueMarshaler implementations. Cleaning up these semantics is necessary so that JniValueManager.CreateValue() can be meaningful. The only time that JniValueManager.CreateValue() won't create a *new* value is if the value is a proxy'd instance, at which point we're not going to muck with it and any resulting bugs are Not Our Problem™. The primary change of killing JniValueManager.GetObject() required a few secondary changes: * JniValueManager.RegisterObject<T>() can no longer throw a NotSupportedException when attempting to create an alias, as (1) the NotSupportedException is a semantic change from Xamarin.Android that we can't accept -- aliases are a fact of life, and will be required for .JavaCast<T> (Issue #10) -- and (2) the check was breaking unit tests. We now generate a warning to the GREF log when aliases are created, similar in spirit to what Xamarin.Android does. * ProxyValueMarshaler.CreateGenericValue() now falls back to JniValueManager.CreateObjectWrapper() instead of returning `null`. This was needed so that ValueManager.GetValue<Exception>(...) would work. * For a "unified type system"-like experience, JniValueManager.CreateObjectWrapper() now maps certain types to corresponding types, e.g. object is mapped to JavaObject, and Exception is mapped to JavaException. This is likewise to support ValueManager.GetValue<Exception>(...) around a Java instance.
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Aug 17, 2016
When `JniRuntime.CreationOptions.DestroyRuntimeOnDispose` is true, `JavaVM::DestroyJavaVM()` will be invoked when the `JniRuntime` instance is disposed *or* finalized. `JreRuntime.CreateJreVM()` would *always* set `DestroyRuntimeOnDispose` to true, because it called `JNI_CreateJavaVM()`, so *of course* you'd want to destroy the Java VM, right? Which brings us to unit tests. I don't know of any "before all test fixtures run" and "after all test fixtures run" extension points, which means: 1. The JVM needs to be created implicitly, "on demand." 2. There's no good way to destroy the JVM created in (1) after all tests have finished executing. Which *really* means that the `JreRuntime` instance is *finalized*, which sets us up for the unholy trifecta of AppDomain unloads, finalizers, and JVM shutdown: For unknown reasons, ~randomly, when running the unit tests (e.g. `make run-tests`), the test runner will *hang*, indefinitely. Attaching `lldb` and triggering a backtrace shows the unholy trifecta: Finalization: thread dotnet#4: tid = 0x403831, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10, name = 'tid_1403' ... frame dotnet#10: 0x00000001001ccb4a mono64`mono_gc_run_finalize(obj=<unavailable>, data=<unavailable>) + 938 at gc.c:256 [opt] frame dotnet#11: 0x00000001001cdd4a mono64`finalizer_thread [inlined] finalize_domain_objects + 51 at gc.c:681 [opt] frame dotnet#12: 0x00000001001cdd17 mono64`finalizer_thread(unused=<unavailable>) + 295 at gc.c:730 [opt] JVM destruction: thread dotnet#4: tid = 0x403831, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10, name = 'tid_1403' frame #0: 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame dotnet#1: 0x00007fffa04d4728 libsystem_pthread.dylib`_pthread_cond_wait + 767 frame dotnet#2: 0x000000010ba5bc76 libjvm.dylib`os::PlatformEvent::park() + 192 frame dotnet#3: 0x000000010ba38e32 libjvm.dylib`ParkCommon(ParkEvent*, long) + 42 frame dotnet#4: 0x000000010ba39708 libjvm.dylib`Monitor::IWait(Thread*, long) + 168 frame dotnet#5: 0x000000010ba398f0 libjvm.dylib`Monitor::wait(bool, long, bool) + 246 frame dotnet#6: 0x000000010bb3dca2 libjvm.dylib`Threads::destroy_vm() + 80 frame dotnet#7: 0x000000010b8fd665 libjvm.dylib`jni_DestroyJavaVM + 254 AppDomain unload: thread dotnet#37: tid = 0x4038fb, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame #0: 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame dotnet#1: 0x00007fffa04d4728 libsystem_pthread.dylib`_pthread_cond_wait + 767 frame dotnet#2: 0x0000000100234a7f mono64`mono_os_cond_timedwait [inlined] mono_os_cond_wait(cond=0x0000000102016e50, mutex=0x0000000102016e10) + 11 at mono-os-mutex.h:105 [opt] frame dotnet#3: 0x0000000100234a74 mono64`mono_os_cond_timedwait(cond=0x0000000102016e50, mutex=0x0000000102016e10, timeout_ms=<unavailable>) + 164 at mono-os-mutex.h:120 [opt] frame dotnet#4: 0x0000000100234828 mono64`_wapi_handle_timedwait_signal_handle(handle=0x0000000000000440, timeout=4294967295, alertable=1, poll=<unavailable>, alerted=0x0000700000a286f4) + 536 at handles.c:1554 [opt] frame dotnet#5: 0x0000000100246370 mono64`wapi_WaitForSingleObjectEx(handle=<unavailable>, timeout=<unavailable>, alertable=<unavailable>) + 592 at wait.c:189 [opt] frame dotnet#6: 0x00000001001c832e mono64`mono_domain_try_unload [inlined] guarded_wait(timeout=4294967295, alertable=1) + 30 at appdomain.c:2390 [opt] frame dotnet#7: 0x00000001001c8310 mono64`mono_domain_try_unload(domain=0x000000010127ccb0, exc=0x0000700000a287a0) + 416 at appdomain.c:2482 [opt] frame dotnet#8: 0x00000001001c7db2 mono64`ves_icall_System_AppDomain_InternalUnload [inlined] mono_domain_unload(domain=<unavailable>) + 20 at appdomain.c:2379 [opt] frame dotnet#9: 0x00000001001c7d9e mono64`ves_icall_System_AppDomain_InternalUnload(domain_id=<unavailable>) + 46 at appdomain.c:2039 [opt] This randomly results in deadlock, and hung Jenkins bots. Fix this behavior by altering `JreRuntime.CreateJreVM()` to *not* override the value of `JniRuntime.CreationOptions.DestroyRuntimeOnDispose`. This allows the constructor of the `JreRuntime` instance to decide whether or not the JVM is destroyed. In the case of TestJVM, we *don't* want to destroy the JVM. This prevents the JVM from being destroyed, which in turn prevents the hang during process shutdown.
jonpryor
added a commit
that referenced
this issue
Aug 17, 2016
When `JniRuntime.CreationOptions.DestroyRuntimeOnDispose` is true, `JavaVM::DestroyJavaVM()` will be invoked when the `JniRuntime` instance is disposed *or* finalized. `JreRuntime.CreateJreVM()` would *always* set `DestroyRuntimeOnDispose` to true, because it called `JNI_CreateJavaVM()`, so *of course* you'd want to destroy the Java VM, right? Which brings us to unit tests. I don't know of any "before all test fixtures run" and "after all test fixtures run" extension points, which means: 1. The JVM needs to be created implicitly, "on demand." 2. There's no good way to destroy the JVM created in (1) after all tests have finished executing. Which *really* means that the `JreRuntime` instance is *finalized*, which sets us up for the unholy trifecta of AppDomain unloads, finalizers, and JVM shutdown: For unknown reasons, ~randomly, when running the unit tests (e.g. `make run-tests`), the test runner will *hang*, indefinitely. Attaching `lldb` and triggering a backtrace shows the unholy trifecta: Finalization: thread #4: tid = 0x403831, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10, name = 'tid_1403' ... frame #10: 0x00000001001ccb4a mono64`mono_gc_run_finalize(obj=<unavailable>, data=<unavailable>) + 938 at gc.c:256 [opt] frame #11: 0x00000001001cdd4a mono64`finalizer_thread [inlined] finalize_domain_objects + 51 at gc.c:681 [opt] frame #12: 0x00000001001cdd17 mono64`finalizer_thread(unused=<unavailable>) + 295 at gc.c:730 [opt] JVM destruction: thread #4: tid = 0x403831, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10, name = 'tid_1403' frame #0: 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame #1: 0x00007fffa04d4728 libsystem_pthread.dylib`_pthread_cond_wait + 767 frame #2: 0x000000010ba5bc76 libjvm.dylib`os::PlatformEvent::park() + 192 frame #3: 0x000000010ba38e32 libjvm.dylib`ParkCommon(ParkEvent*, long) + 42 frame #4: 0x000000010ba39708 libjvm.dylib`Monitor::IWait(Thread*, long) + 168 frame #5: 0x000000010ba398f0 libjvm.dylib`Monitor::wait(bool, long, bool) + 246 frame #6: 0x000000010bb3dca2 libjvm.dylib`Threads::destroy_vm() + 80 frame #7: 0x000000010b8fd665 libjvm.dylib`jni_DestroyJavaVM + 254 AppDomain unload: thread #37: tid = 0x4038fb, 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame #0: 0x00007fff9656bdb6 libsystem_kernel.dylib`__psynch_cvwait + 10 frame #1: 0x00007fffa04d4728 libsystem_pthread.dylib`_pthread_cond_wait + 767 frame #2: 0x0000000100234a7f mono64`mono_os_cond_timedwait [inlined] mono_os_cond_wait(cond=0x0000000102016e50, mutex=0x0000000102016e10) + 11 at mono-os-mutex.h:105 [opt] frame #3: 0x0000000100234a74 mono64`mono_os_cond_timedwait(cond=0x0000000102016e50, mutex=0x0000000102016e10, timeout_ms=<unavailable>) + 164 at mono-os-mutex.h:120 [opt] frame #4: 0x0000000100234828 mono64`_wapi_handle_timedwait_signal_handle(handle=0x0000000000000440, timeout=4294967295, alertable=1, poll=<unavailable>, alerted=0x0000700000a286f4) + 536 at handles.c:1554 [opt] frame #5: 0x0000000100246370 mono64`wapi_WaitForSingleObjectEx(handle=<unavailable>, timeout=<unavailable>, alertable=<unavailable>) + 592 at wait.c:189 [opt] frame #6: 0x00000001001c832e mono64`mono_domain_try_unload [inlined] guarded_wait(timeout=4294967295, alertable=1) + 30 at appdomain.c:2390 [opt] frame #7: 0x00000001001c8310 mono64`mono_domain_try_unload(domain=0x000000010127ccb0, exc=0x0000700000a287a0) + 416 at appdomain.c:2482 [opt] frame #8: 0x00000001001c7db2 mono64`ves_icall_System_AppDomain_InternalUnload [inlined] mono_domain_unload(domain=<unavailable>) + 20 at appdomain.c:2379 [opt] frame #9: 0x00000001001c7d9e mono64`ves_icall_System_AppDomain_InternalUnload(domain_id=<unavailable>) + 46 at appdomain.c:2039 [opt] This randomly results in deadlock, and hung Jenkins bots. Fix this behavior by altering `JreRuntime.CreateJreVM()` to *not* override the value of `JniRuntime.CreationOptions.DestroyRuntimeOnDispose`. This allows the constructor of the `JreRuntime` instance to decide whether or not the JVM is destroyed. In the case of TestJVM, we *don't* want to destroy the JVM. This prevents the JVM from being destroyed, which in turn prevents the hang during process shutdown.
jpobst
added
enhancement
Proposed change to current functionality
java-interop
Runtime bridge between .NET and Java
labels
Apr 16, 2020
jonpryor
added a commit
to jonpryor/java.interop
that referenced
this issue
Oct 29, 2020
Changes: mono/LineEditor@3fa0c2e...5d67d34 * mono/LineEditor@5d67d34: Use the new signing features for RealSigning (dotnet#10)
jonpryor
added a commit
that referenced
this issue
Oct 29, 2020
Changes: mono/LineEditor@3fa0c2e...5d67d34 * mono/LineEditor@5d67d34: Use the new signing features for RealSigning (#10)
jonpryor
added a commit
that referenced
this issue
Jul 8, 2024
Fixes: #10 Fixes: dotnet/android#9038 Context: 1adb796 Imagine the following Java type hierarchy: // Java public abstract class Drawable { public static Drawable createFromStream(IntputStream is, String srcName) {…} // … } public interface Animatable { public void start(); // … } /* package */ class SomeAnimatableDrawable extends Drawable implements Animatable { // … } Further imagine that a call to `Drawable.createFromStream()` returns an instance of `SomeAnimatableDrawable`. What does the *binding* `Drawable.CreateFromStream()` return? // C# var drawable = Drawable.CreateFromStream(input, name); // What is the runtime type of `drawable`? The binding `Drawable.CreateFromStream()` look at the runtime type of the value returned, sees that it's of type `SomeAnimatableDrawable`, and looks for an existing binding of that type. If no such binding is found -- which will be the case here, as `SomeAnimatableDrawable` is package-private -- then we check the value's base class, ad infinitum, until we hit a type that we *do* have a binding for (or fail catastrophically if we can't find a binding for `java.lang.Object`). See also [`TypeManager.CreateInstance()`][0], which is similar to the code within `JniRuntime.JniValueManager.GetPeerConstructor()`. Any interfaces implemented by Java value are not consulted, only the base class hierarchy is consulted. Consequently, the runtime type of `drawable` would be the `Drawable` binding; however, as `Drawable` is an `abstract` type, the runtime type will *actually* be `DrawableInvoker` (see e.g. 1adb796), akin to: // emitted by `generator`… internal class DrawableInvoker : Drawable { // … } Further imagine that we want to invoke `Animatable` methods on `drawable`. How do we do this? This is where the [`.JavaCast<TResult>()` extension method][1] comes in: we can use `.JavaCast<TResult>()` to perform a Java-side type check for the desired type, which returns a value which can be used to invoke methods on the specified type: var animatable = drawable.JavaCast<IAnimatable>(); animatable.Start(); The problem with `.JavaCast<TResult>()` is that it always throws on failure: var someOtherIface = drawable.JavaCast<ISomethingElse>(); // throws some exception… @mattleibow requests an "exception-free JavaCast overload" so that he can *easily* use type-specific functionality *optionally*. Add the following extension methods to `IJavaPeerable`: static partial class JavaPeerableExtensions { public static TResult? JavaAs<TResult>( this IJavaPeerable self); public static bool TryJavaCast<TResult>( this IJavaPeerable self, out TResult? result); } The `.JavaAs<TResult>()` extension method mirrors the C# `as` operator, returning `null` if the the runtime type of `self` is not implicitly convertible to the Java type corresponding to `TResult`. This makes it useful for one-off invocations: drawable.JavaAs<IAnimatable>()?.Start(); The `.TryJavaCast<TResult>()` extension method follows the [`TryParse()` pattern][2], returning true if the type coercion succeeds and the output `result` parameter is non-null, and false otherwise. This allows "nicely scoping" things within an `if`: if (drawable.TryJavaCast<IAnimatable>(out var animatable)) { animatable.Start(); // … animatable.Stop(); } [0]: https://github.com/dotnet/android/blob/06bb1dc6a292ef5618a3bb6ecca3ca869253ff2e/src/Mono.Android/Java.Interop/TypeManager.cs#L276-L291 [1]: https://github.com/dotnet/android/blob/06bb1dc6a292ef5618a3bb6ecca3ca869253ff2e/src/Mono.Android/Android.Runtime/Extensions.cs#L9-L17 [2]: https://learn.microsoft.com/dotnet/standard/design-guidelines/exceptions-and-performance#try-parse-pattern
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
enhancement
Proposed change to current functionality
java-interop
Runtime bridge between .NET and Java
Java.Interop.dll assembly “needs” a .JavaCast extension method, but the question is how to “sanely” provide one when Mono.Android.dll provides one w/o causing compilation errors
The text was updated successfully, but these errors were encountered: