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

[Java.Runtime.Environment] Partial support for .NET Core #804

Merged
merged 1 commit into from
Feb 23, 2021

Conversation

jonpryor
Copy link
Member

@jonpryor jonpryor commented Feb 18, 2021

Enable C#8 Nullable Reference Types for
Java.Runtime.Environment.dll.

Add partial support for a "non-bridged backend", so that a
JniRuntime.JniValueManager exists for .NET Core. This new
"managed" backed is used if the Mono runtime is not used.

To work, ManagedValueManager holds strong references to
IJavaPeerable instances. As such, tests which required the use of
GC integration are now "optional", conditional on the
!NO_GC_BRIDGE_SUPPORT define.

The ManagedValueManager.CollectPeers() method calls
IJavaPeerable.Dispose() on all currently referenced peers, then
stops referencing the managed peers.
This causes all GREFs to be dropped, allowing Java peers to be
collected, and then allows the .NET GC to collect the IJavaPeerable
values.

Any and all exceptions thrown by IJavaPeerable.Dispose() are
caught and re-thrown by an AggregateException.

Update Java.Interop-Tests.csproj to define NO_GC_BRIDGE_SUPPORT
and NO_MARSHAL_MEMBER_BUILDER_SUPPORT when building for .NET Core.
This excludes all currently "troublesome"/non-passing tests.

These changes allow all remaining Java.Interop-Tests unit tests to
execute under .NET Core:

    % dotnet test -v diag '--logger:trx;verbosity=detailed' bin/TestDebug-netcoreapp3.1/Java.Interop-Tests.dll
    Passed!  - Failed:     0, Passed:   617, Skipped:     1, Total:   618, Duration: 1 s

Other changes:

  • The attempt to retain useful Java-side exceptions in 89a5a22
    proved to be incomplete. Add a comment to invoke
    JNIEnv::ExceptionDescribe(). We don't always want this
    to be present, but when we do want it…

  • While NO_MARSHAL_MEMBER_BUILDER_SUPPORT is set -- which means
    that Java.Interop.Export-related tests aren't run -- there are
    some fixes for Java.Interop.Export & related unit tests for
    .NET Core, to avoid the use of generic delegate types and to
    avoid a Type.GetType() which is no longer needed.

@jonpryor jonpryor requested review from radekdoulik and jpobst and removed request for radekdoulik February 18, 2021 22:47
@jonpryor
Copy link
Member Author

@radekdoulik , @jpobst : thoughts on the new API additions? Are current ReleasePeers() semantics a good idea, in which the peers are simply "released" (GREFs freed, a managed root removed) w/ no notification to the peers?

Or should we treat it "as if" they were disposed or finalized?

@jonpryor
Copy link
Member Author

Related discussion & elaboration: #426 (comment)

@jonpryor
Copy link
Member Author

@jpobst: if you know how to filter a YAML -task so that it only runs on macOS hosts, we can enable the Java.Interop-Tests.dll run for dotnet test. Otherwise we'll need to wait until we have a Windows build of src/java-interop

(…which shouldn't be that hard. We'll just want to write a CMakeLists.txt for it and require cmake to build…)

@jonpryor
Copy link
Member Author

Design thoughts…

Instead of a bool PeersRequireRelease {get;} property, perhaps it should be an enum of some form?

ReleasePeers() doesn't need to be the abstraction; it could be implemented in terms of GetSurfacedPeers(). It could be an extension method. (Not that it should, but it could…)

Additionally, ReleasePeers() is too much.

A scenario behind #426 was e.g. to do a "C# plugin for a machine learning pipeline": have Tensorflow+Java installed on a server, write a plugin for it within C#, and then process lots of images in a pipeline with the C# plugin.

In such an environment, the normal GC bridge isn't necessarily required: between each image you can "release everything" and do some GC's so that you don't leak.

My suspicion, though, is that you wouldn't want to release everything. You'd just want to release "everything created since some well-defined point". This would allow one to initialize things once, get a good "starting state", and then process the pipeline, without needn't to re-initialize between each item.

ReleasePeers() does not work with such a construct. It nukes everything.

I think we need a better abstraction than what is proposed here. Probably something "stack-like".

jonpryor pushed a commit that referenced this pull request Feb 19, 2021
Context: #804

Two changes within commit 89a5a22 allow `dotnet test` to be used
to run the net472 mono-required NUnit tests:

 1. The `java-interop` native library is reliably copied to the
    `$(OutputPath)` of referencing projects.  This ensures that e.g.
    `bin/TestDebug/libjava-interop.dylib` exists without extra `make`
    commands or an `msbuild /t:RunTests` invocation.

 2. More importantly, `tests/TestJVM` was updated to use
    `Xamarin.Android.Tools.AndroidSdk.dll` along with
    `JdkInfo.GetKnownSystemJdkInfos()`.

(2) means that the `JI_JVM_PATH` environment variable doesn't *need*
to be set before running unit tests, and is largely responsible for
allowing `dotnet test` to work:

	% make prepare all
	% dotnet test bin/TestDebug/Java.Interop-Tests.dll
	…
	Passed!  - Failed:     0, Passed:   631, Skipped:     1, Total:   632, Duration: 686 ms

Note that these tests are *not* executed under .NET Core/CoreCLR!
It's simply using `dotnet test` as an equivalent to/replacement for
`mono nunit3-console.exe`.

Java.Interop-Tests to be run under .NET Core are built into e.g.
`bin/TestDebug-netcoreapp3.1/Java.Interop-Tests.dll`, and trying to
run *that* test suite crashes and burns:

	% dotnet test bin/TestDebug-netcoreapp3.1/Java.Interop-Tests.dll
	…
	Failed!  - Failed:    16, Passed:   325, Skipped:     1, Total:   342, Duration: 311 ms

See e.g. PR #804 for an attempt to make .NET Core support work.
@jonpryor jonpryor changed the title [Java.Runtime.Environment] Support .NET Core [Java.Runtime.Environment] Partial support for .NET Core Feb 20, 2021
@jonpryor
Copy link
Member Author

In the interest of expediency, I've removed API additions which attempt to fix Issue #426. What remains is "potentially weird" -- the JniRuntime.JniValueManager.CollectPeers() override in ManagedValueManager releases all values -- the result of which is that three tests are disabled if NO_GC_BRIDGE_SUPPORT is defined, which is the case when building for netcoreapp3.1. (This is in addition to the "original" inclusion of NO_MARSHAL_MEMBER_BUILDER_SUPPORT, so I could worry about fixing Java.Interop.Export "later".)

There is thus no API breakage, and no new semantics to review here. Issue #426 will contain the primary discussion of how to "properly" support a non-bridging GC environment.

Copy link
Contributor

@jpobst jpobst left a comment

Choose a reason for hiding this comment

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

It looks fine to me, though not really my area of expertise. Hopefully Radek can review better than me!

@@ -47,6 +48,8 @@ public static unsafe JniObjectReference FindClass (string classname)
return r;
}

// NativeMethods.java_interop_jnienv_exception_describe (info.EnvironmentPointer);
Copy link
Contributor

Choose a reason for hiding this comment

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

dead code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, but not entirely; from the commit message:

The attempt to retain useful Java-side exceptions in 89a5a22
proved to be incomplete. Add a comment to invoke
JNIEnv::ExceptionDescribe(). We don't always want this
to be present, but when we do want it…

@@ -370,7 +370,12 @@ static Expression GetRuntime ()
return Expression.Property (null, typeof (JniEnvironment), "Runtime");
}

static MethodInfo FormatterServices_GetUninitializedObject = Type.GetType ("System.Runtime.Serialization.FormatterServices", throwOnError: true)
static MethodInfo FormatterServices_GetUninitializedObject =
#if NETFRAMEWORK || NET_2_0
Copy link
Member

Choose a reason for hiding this comment

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

#if NETCOREAPP might be simpler here (plus switch the branches). Nothing important though.

Copy link
Member Author

Choose a reason for hiding this comment

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

When we start targeting .NET 5/6, won't we also need NET? Meaning we'd likewise want #if NETCOREAPP || NET? Which doesn't actually seem simpler…

Copy link
Member

Choose a reason for hiding this comment

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

net5, net6, ... also define NETCOREAPP

builder.ValueManager = builder.ValueManager ?? new ManagedValueManager ();
builder.ObjectReferenceManager = builder.ObjectReferenceManager ?? new ManagedObjectReferenceManager (builder.JniGlobalReferenceLogWriter, builder.JniLocalReferenceLogWriter);
}

Copy link
Member

Choose a reason for hiding this comment

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

Nice. We don't have to create the managers before creating the VM. I like that change.

var peers = new List<IJavaPeerable> ();

lock (RegisteredInstances) {
foreach (var ps in RegisteredInstances.Values) {
Copy link
Member

Choose a reason for hiding this comment

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

Here it might be more efficient to do:

                       List<IDisposable> values;
                       lock (RegisteredInstances) {
                               values = new List<IDisposable> (RegisteredInstances.Values);
                               RegisteredInstances.Clear ();
                        }

and do the rest of cleaning after that? It will also lower time spent in lock.

Copy link
Member Author

Choose a reason for hiding this comment

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

RegisteredInstances.Values is of type ICollection<List<IJavaPeerable>>, not ICollection<IJavaPeerable>. Thus, new List<IDisposable> (RegisteredInstances.Values) won't work.

Copy link
Member

Choose a reason for hiding this comment

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

I was updating the code from elswhere, so it would need to be new List<List<IJavaPeerable>> (RegisteredInstances.Values), the idea is the same though. To leave the lock block quickly.

Enable C#8 [Nullable Reference Types][0] for
`Java.Runtime.Environment.dll`.

Add partial support for a "non-bridged backend", so that a
`JniRuntime.JniValueManager` exists for .NET Core.  This new
"managed" backed is used if the Mono runtime is *not* used.

To work, `ManagedValueManager` holds *strong* references to
`IJavaPeerable` instances.  As such, tests which required the use of
GC integration are now "optional", conditional on the
`!NO_GC_BRIDGE_SUPPORT` define.

The `ManagedValueManager.CollectPeers()` method calls
`IJavaPeerable.Dispose()` on all currently referenced peers, then
stops referencing the managed peers.
This causes all GREFs to be dropped, allowing Java peers to be
collected, and then allows the .NET GC to collect the `IJavaPeerable`
values.

Any and all exceptions thrown by `IJavaPeerable.Dispose()` are
caught and re-thrown by an `AggregateException`.

Update `Java.Interop-Tests.csproj` to define `NO_GC_BRIDGE_SUPPORT`
and `NO_MARSHAL_MEMBER_BUILDER_SUPPORT` when building for .NET Core.
This excludes all currently "troublesome"/non-passing tests.

These changes allow all remaining `Java.Interop-Tests` unit tests to
execute under .NET Core:

	% dotnet test -v diag '--logger:trx;verbosity=detailed' bin/TestDebug-netcoreapp3.1/Java.Interop-Tests.dll
	Passed!  - Failed:     0, Passed:   617, Skipped:     1, Total:   618, Duration: 1 s

Other changes:

  * The attempt to retain useful Java-side exceptions in 89a5a22
    proved to be incomplete.  Add a comment to invoke
    [`JNIEnv::ExceptionDescribe()`][1].  We don't always want this
    to be present, but when we do want it…

  * While `NO_MARSHAL_MEMBER_BUILDER_SUPPORT` is set -- which means
    that `Java.Interop.Export`-related tests aren't run -- there are
    some fixes for `Java.Interop.Export` & related unit tests for
    .NET Core, to avoid the use of generic delegate types and to
    avoid a `Type.GetType()` which is no longer needed.

[0]: https://docs.microsoft.com/dotnet/csharp/nullable-references
[1]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#ExceptionDescribe
@jonpryor jonpryor merged commit a666a6f into dotnet:main Feb 23, 2021
@jpobst jpobst added this to the 11.3 (16.10 / 8.10) milestone Mar 1, 2021
@github-actions github-actions bot locked and limited conversation to collaborators Apr 12, 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.

3 participants