forked from dotnet/java-interop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Runtime.Environment] Support .NET Core
Fixes: dotnet#426 Enable C#8 [Nullable Reference Types][0] for `Java.Runtime.Environment.dll`. Add 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". To make this work, update `JniRuntime.JniValueManager` to have the following new abstract members: partial class JniRuntime { partial class JniValueManager { public abstract bool PeersRequireRelease {get;} public abstract void ReleasePeers (); } } `PeersRequireRelease` shall be `true` when there is no GC bridge. When this is true, `JniValueManager.CollectPeers()` is a no-op. If an `IJavaPeerable` must have disposal logic performed, then `.Dispose()` must be called on that instance. There is no finalization integration. The new `JniValueManager.ReleasePeers()` method: 1. Releases all GREFs for all held peers. This allows Java to collect the Java peers. 2. Stops referencing all `IJavaPeerable` values. This allows the .NET GC to collect the `IJavaPeerable` values. There is no notification to the `IJavaPeerable` instances that this has happened. Update `Java.Interop-Tests.csproj` to define `NO_MARSHAL_MEMBER_BUILDER_SUPPORT` when building for .NET Core. 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 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
- Loading branch information
Showing
18 changed files
with
614 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
228 changes: 228 additions & 0 deletions
228
src/Java.Runtime.Environment/Java.Interop/ManagedObjectReferenceManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using System.Threading; | ||
|
||
namespace Java.Interop { | ||
|
||
class ManagedObjectReferenceManager : JniRuntime.JniObjectReferenceManager { | ||
|
||
TextWriter? grefLog; | ||
TextWriter? lrefLog; | ||
|
||
int grefCount; | ||
int wgrefCount; | ||
|
||
|
||
public override int GlobalReferenceCount => grefCount; | ||
public override int WeakGlobalReferenceCount => wgrefCount; | ||
|
||
public override bool LogLocalReferenceMessages => lrefLog != null; | ||
public override bool LogGlobalReferenceMessages => grefLog != null; | ||
|
||
public ManagedObjectReferenceManager (TextWriter? grefLog, TextWriter? lrefLog) | ||
{ | ||
if (grefLog != null && lrefLog != null && object.ReferenceEquals (grefLog, lrefLog)) { | ||
this.grefLog = this.lrefLog = TextWriter.Synchronized (grefLog); | ||
return; | ||
} | ||
|
||
var grefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_GREF_LOG"); | ||
var lrefPath = Environment.GetEnvironmentVariable ("JAVA_INTEROP_LREF_LOG"); | ||
|
||
bool samePath = !string.IsNullOrEmpty (grefPath) && | ||
!string.IsNullOrEmpty (lrefPath) && | ||
grefPath == lrefPath; | ||
|
||
if (grefLog != null) { | ||
this.grefLog = TextWriter.Synchronized (grefLog); | ||
} | ||
if (lrefLog != null) { | ||
this.lrefLog = TextWriter.Synchronized (lrefLog); | ||
} | ||
|
||
if (this.grefLog == null && !string.IsNullOrEmpty (grefPath)) { | ||
this.grefLog = TextWriter.Synchronized (CreateTextWriter (grefPath)); | ||
} | ||
if (this.lrefLog == null && samePath) { | ||
this.lrefLog = this.grefLog; | ||
} | ||
if (this.lrefLog == null && !string.IsNullOrEmpty (lrefPath)) { | ||
this.lrefLog = TextWriter.Synchronized (CreateTextWriter (lrefPath)); | ||
} | ||
} | ||
|
||
public override void OnSetRuntime (JniRuntime runtime) | ||
{ | ||
base.OnSetRuntime (runtime); | ||
} | ||
|
||
static TextWriter? CreateTextWriter (string path) | ||
{ | ||
return new StreamWriter (path, append: false, encoding: new UTF8Encoding (encoderShouldEmitUTF8Identifier: false)); | ||
} | ||
|
||
public override void WriteLocalReferenceLine (string format, params object[] args) | ||
{ | ||
if (lrefLog == null) | ||
return; | ||
lrefLog.WriteLine (format, args); | ||
lrefLog.Flush (); | ||
} | ||
|
||
public override JniObjectReference CreateLocalReference (JniObjectReference reference, ref int localReferenceCount) | ||
{ | ||
if (!reference.IsValid) | ||
return reference; | ||
|
||
var r = base.CreateLocalReference (reference, ref localReferenceCount); | ||
|
||
CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, r, Runtime); | ||
|
||
return r; | ||
} | ||
|
||
public override void DeleteLocalReference (ref JniObjectReference reference, ref int localReferenceCount) | ||
{ | ||
if (!reference.IsValid) | ||
return; | ||
|
||
var r = reference; | ||
|
||
base.DeleteLocalReference (ref reference, ref localReferenceCount); | ||
|
||
DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); | ||
} | ||
|
||
public override void CreatedLocalReference (JniObjectReference reference, ref int localReferenceCount) | ||
{ | ||
if (!reference.IsValid) | ||
return; | ||
base.CreatedLocalReference (reference, ref localReferenceCount); | ||
CreatedReference (lrefLog, "+l+ lrefc", localReferenceCount, reference, Runtime); | ||
} | ||
|
||
public override IntPtr ReleaseLocalReference (ref JniObjectReference reference, ref int localReferenceCount) | ||
{ | ||
if (!reference.IsValid) | ||
return IntPtr.Zero; | ||
var r = reference; | ||
var p = base.ReleaseLocalReference (ref reference, ref localReferenceCount); | ||
DeletedReference (lrefLog, "-l- lrefc", localReferenceCount, r, Runtime); | ||
return p; | ||
} | ||
|
||
public override void WriteGlobalReferenceLine (string format, params object?[]? args) | ||
{ | ||
if (grefLog == null) | ||
return; | ||
grefLog.WriteLine (format, args); | ||
grefLog.Flush (); | ||
} | ||
|
||
public override JniObjectReference CreateGlobalReference (JniObjectReference reference) | ||
{ | ||
if (!reference.IsValid) | ||
return reference; | ||
var n = base.CreateGlobalReference (reference); | ||
int c = Interlocked.Increment (ref grefCount); | ||
CreatedReference (grefLog, "+g+ grefc", c, reference, n, Runtime); | ||
return n; | ||
} | ||
|
||
public override void DeleteGlobalReference (ref JniObjectReference reference) | ||
{ | ||
if (!reference.IsValid) | ||
return; | ||
int c = Interlocked.Decrement (ref grefCount); | ||
DeletedReference (grefLog, "-g- grefc", c, reference, Runtime); | ||
base.DeleteGlobalReference (ref reference); | ||
} | ||
|
||
public override JniObjectReference CreateWeakGlobalReference (JniObjectReference reference) | ||
{ | ||
if (!reference.IsValid) | ||
return reference; | ||
var n = base.CreateWeakGlobalReference (reference); | ||
|
||
int wc = Interlocked.Increment (ref wgrefCount); | ||
int gc = grefCount; | ||
if (grefLog != null) { | ||
string message = $"+w+ grefc {gc} gwrefc {wc} obj-handle {reference.ToString ()} -> new-handle {n.ToString ()} " + | ||
$"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + | ||
Environment.NewLine + | ||
Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); | ||
grefLog.WriteLine (message); | ||
grefLog.Flush (); | ||
} | ||
|
||
return n; | ||
} | ||
|
||
public override void DeleteWeakGlobalReference (ref JniObjectReference reference) | ||
{ | ||
if (!reference.IsValid) | ||
return; | ||
|
||
int wc = Interlocked.Decrement (ref wgrefCount); | ||
int gc = grefCount; | ||
|
||
if (grefLog != null) { | ||
string message = $"-w- grefc {gc} gwrefc {wc} handle {reference.ToString ()} " + | ||
$"from thread '{Runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + | ||
Environment.NewLine + | ||
Runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); | ||
grefLog.WriteLine (message); | ||
grefLog.Flush (); | ||
} | ||
|
||
base.DeleteWeakGlobalReference (ref reference); | ||
} | ||
|
||
protected override void Dispose (bool disposing) | ||
{ | ||
} | ||
|
||
static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) | ||
{ | ||
if (writer == null) | ||
return; | ||
string message = $"{kind} {count} handle {reference.ToString ()} " + | ||
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + | ||
Environment.NewLine + | ||
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); | ||
writer.WriteLine (message); | ||
writer.Flush (); | ||
} | ||
|
||
static void CreatedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniObjectReference newReference, JniRuntime runtime) | ||
{ | ||
if (writer == null) | ||
return; | ||
string message = $"{kind} {count} obj-handle {reference.ToString ()} -> new-handle {newReference.ToString ()} " + | ||
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + | ||
Environment.NewLine + | ||
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); | ||
writer.WriteLine (message); | ||
writer.Flush (); | ||
} | ||
|
||
static void DeletedReference (TextWriter? writer, string kind, int count, JniObjectReference reference, JniRuntime runtime) | ||
{ | ||
if (writer == null) | ||
return; | ||
string message = $"{kind} {count} handle {reference.ToString ()} " + | ||
$"from thread '{runtime.GetCurrentManagedThreadName ()}'({Environment.CurrentManagedThreadId})" + | ||
Environment.NewLine + | ||
runtime.GetCurrentManagedThreadStackTrace (skipFrames: 2, fNeedFileInfo: true); | ||
writer.WriteLine (message); | ||
writer.Flush (); | ||
} | ||
} | ||
} |
Oops, something went wrong.