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.
Fixes: dotnet#4 Context: dotnet#426 Alternate names? * JavaPeerableCleanupPool * JavaPeerCleanup * JavaReferenceCleanup * JniPeerRegistrationScope Issue dotnet#426 is an idea to implement a *non*-GC-Bridged `JniRuntime.JniValueManager` type, primarily for use with .NET Core. This was begun in a666a6f. What's missing is the answer to a question: what do do about `JniRuntime.JniValueManager.CollectPeers()`? With a Mono-style GC bridge, `CollectPeers()` is `GC.Collect()`. In a666a6f with .NET Core, `CollectPeers()` calls `IJavaPeerable.Dispose()` on all registered instances, which is "extreme". @jonpryor thought that if there were a *scope-based* way to selectively control which instances were disposed, that might be "better" and more understandable. Plus, this is issue dotnet#4! Add `JavaScope`, which introduces a scope-based mechanism to control when `IJavaPeerable` instances are cleaned up: public enum JavaScopeCleanup { RegisterWithManager, Dispose, Release, } public ref struct JavaScope { public JavaScope(JavaScopeCleanup cleanup); public void Dispose(); } `JavaScope` is a [`ref struct`][0], which means it can only be allocated on the runtime stack, ensuring that cleanup semantics are *scope* semantics. TODO: is that actually a good idea? If a `JavaScope` is created using `JavaScopeCleanup.RegisterWithManager`, existing behavior is followed. This is useful for nested scopes, should instances need to be registered with `JniRuntime.ValueManager`. If a `JavaScope` is created using `JavaScopeCleanup.Dispose` or `JavaScopeCleanup.Release`, then: 1. Object references created within the scope are "thread-local"; they can be *used* by other threads, but `JniRuntime.JniValueManager.PeekPeer()` won't find existing values. 2. At the end of a `using` block / when `JavaScope.Dispose()` is called, all collected instances will be `Dispose()`d (with `.Dispose`) or released (with `.Release`), and left to the GC to eventually finalize. For example: using (new JavaScope (JavaScopeCleanup.Dispose)) { var singleton = JavaSingleton.Singleton; // use singleton } // `singleton.Dispose()` is called at the end of the `using` block TODO: docs? TODO: *nested* scopes, and "bound" vs. "unbound" instance construction around `JniValueManager.GetValue<T>()` or `.CreateValue<T>()`, and *why* they should be treated differently. TODO: Should `CreateValue<T>()` be *removed*? name implies it always "creates" a new value, but implementation will return existing instances, so `GetValue<T>()` alone may be better. One related difference is that` `CreateValue<T>()` uses `PeekBoxedObject()`, while `GetValue<T>()` doesn't. *Should* it? [0]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#ref-struct
- Loading branch information
Showing
19 changed files
with
1,217 additions
and
239 deletions.
There are no files selected for viewing
193 changes: 193 additions & 0 deletions
193
src/Java.Interop/Documentation/Java.Interop/JniRuntime.JniValueManager.xml
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,193 @@ | ||
<?xml version="1.0"?> | ||
<docs> | ||
<member name="T:JniValueManager"> | ||
<summary> | ||
Manages the mapping between Java instances and registered managed instances. | ||
</summary> | ||
<remarks> | ||
<para> | ||
<c>JniRuntime.JniValueManager</c> manages a mapping between Java instances, | ||
and <see cref="T:IJavaPeerable" /> instances. A <c>IJavaPeerable</c> is | ||
<i>registered</i> if it has been added to <c>JniRuntime.JniValueManager</c>. | ||
Certain methods only deal with registered instances. Managed-to-Java instance | ||
method invocation does not require the use of registered instances. | ||
Java-to-Managed instance method invocation requires the use of registred | ||
instances; if possible, a managed peer will be implicitly created. | ||
See <see cref="M:GetValue" /> for details. | ||
</para> | ||
<list type="bullet"> | ||
<item><term> | ||
Marshaling infrastructure: | ||
<see cref="M:WaitForGCBridgeProcessing" />. | ||
</term></item> | ||
<item><term> | ||
Lifetime management of all registered peers: | ||
<see cref="M:CollectPeers" />, | ||
<see cref="M:DisposePeers" />, | ||
<see cref="M:ReleasePeers" />. | ||
</term></item> | ||
<item><term> | ||
Registration management for individual peers: | ||
<see cref="M:ActivatePeer" />, | ||
<see cref="M:AddPeer" />, | ||
<see cref="M:ConstructPeer" />, | ||
<see cref="M:DisposePeer" />, | ||
<see cref="M:RemovePeer" />. | ||
</term></item> | ||
<item><term> | ||
Java-to-managed marshaling support: | ||
<see cref="M:GetValue{T}" />, | ||
<see cref="M:GetValue" />, | ||
<see cref="M:GetValueMarshaler" />. | ||
</term></item> | ||
<item><term> | ||
Managed-to-Java marshaling support: | ||
<see cref="M:GetValueMarshaler" />. | ||
</term></item> | ||
</list> | ||
<para> | ||
Managed-to-Java marshaling support is handled via explicit usage, | ||
or "generically" via <see cref="T:JniValueMarshaler" /> and | ||
<see cref="T:JniValueMarshaler{T}" />. | ||
</para> | ||
<block subset="none" type="overrides"> | ||
All subclasses must be thread safe. | ||
</block> | ||
</remarks> | ||
<threadsafe>This type is thread safe.</threadsafe> | ||
</member> | ||
<!-- | ||
Marshaling Infrastructure | ||
--> | ||
<member name="M:WaitForGCBridgeProcessing"> | ||
<summary> | ||
Infrastructure. Called during Java-to-managed transitions. | ||
</summary> | ||
</member> | ||
<!-- | ||
Global peer lifetime maangement | ||
--> | ||
<member name="P:CanCollectPeers"> | ||
<summary> | ||
Whether or not <see cref="M:CollectPeers" /> is supported. | ||
</summary> | ||
<remarks> | ||
<para> | ||
When <c>CanCollectPeers</c> returns <c>false</c>, calls to | ||
<see cref="M:CollectPeers" /> will throw | ||
<see cref="T:System.NotSupportedException" />. | ||
</para> | ||
</remarks> | ||
</member> | ||
<member name="M:CollectPeers"> | ||
<summary> | ||
Garbage collects all peer instances. | ||
</summary> | ||
<remarks> | ||
<para> | ||
When <c>CanCollectPeers</c> returns <c>false</c>, calls to | ||
<see cref="M:CollectPeers" /> will throw | ||
<see cref="T:System.NotSupportedException" />. | ||
</para> | ||
</remarks> | ||
<exception cref="T:System.NotSupportedException"> | ||
Garbage collection of peers is not supported. | ||
</exception> | ||
<exception cref="T:System.ObjectDisposedException"> | ||
<see cref="M:Dispose()" /> has previously been invoked. | ||
</exception> | ||
</member> | ||
<member name="M:CollectPeersCore"> | ||
<summary> | ||
Garbage collects all peer instances. | ||
</summary> | ||
<remarks> | ||
<block subset="none" type="overrides"> | ||
<para>The <c>CollectPeersCore</c> method will not be invoked | ||
after <c>Dispose()</c> has been invoked.</para> | ||
</block> | ||
</remarks> | ||
<exception cref="T:System.NotSupportedException"> | ||
Garbage collection of peers is not supported. | ||
</exception> | ||
</member> | ||
<member name="M:DisposePeers"> | ||
<summary> | ||
Dispose of all registered peer instances. | ||
</summary> | ||
<remarks> | ||
<para> | ||
Calls <see cref="M:System.IDisposable.Dispose" /> on all peer instances. | ||
</para> | ||
</remarks> | ||
<exception cref="T:System.AggregateException"> | ||
Contains all exceptions thrown by registered instances when calling | ||
<see cref="M:System.IDisposable.Dispose" />. | ||
</exception> | ||
<exception cref="T:System.ObjectDisposedException"> | ||
<see cref="M:Dispose" /> has previously been invoked. | ||
</exception> | ||
</member> | ||
<member name="M:DisposePeersCore"> | ||
<summary> | ||
Dispose of all registered peer instances. | ||
</summary> | ||
<remarks> | ||
<block subset="none" type="overrides"> | ||
<para> | ||
The <c>DisposePeersCore</c> method will not be invoked | ||
after <see cref="M:Dispose" /> has been invoked.</para> | ||
<para>Inheritors should invoke <see cref="M:System.IDisposable.Dispose" /> | ||
on all peer instances. Should any peer throw from the <c>Dispose</c> | ||
invocation, then <c>DisposePeersCore</c> should capture all thrown | ||
exceptions and re-raise them within a <see cref="T:System.AggregateException" />. | ||
</para> | ||
</block> | ||
</remarks> | ||
<exception cref="T:System.AggregateException"> | ||
Contains all exceptions thrown by <see cref="M:System.IDisposable.Dispose" />. | ||
</exception> | ||
</member> | ||
<member name="M:ReleasePeers"> | ||
<summary> | ||
Release all registered peer instances. | ||
</summary> | ||
<remarks> | ||
<para> | ||
The <c>JniValueManager</c> unregisters all peers. | ||
Methods such as <see cref="M:PeekPeer" /> will not find return any peers. | ||
</para> | ||
<para> | ||
Peer values may still be used, even if not referenced by a <c>JniValueManager</c>. | ||
</para> | ||
</remarks> | ||
<exception cref="T:System.ObjectDisposedException"> | ||
<see cref="M:Dispose" /> has previously been invoked. | ||
</exception> | ||
</member> | ||
<member name="M:ReleasePeersCore"> | ||
<summary> | ||
Release all registered peer instances. | ||
</summary> | ||
<remarks> | ||
<block subset="none" type="overrides"> | ||
<para>The <c>ReleasePeersCore</c> method will not be invoked | ||
after <c>Dispose()</c> has been invoked.</para> | ||
</block> | ||
</remarks> | ||
</member> | ||
<!-- | ||
Lifetime management for individual peers | ||
--> | ||
<member name="M:AddPeer"> | ||
<summary> | ||
Register a managed peer. | ||
</summary> | ||
<remarks> | ||
<block subset="none" type="overrides"> | ||
<para>The <c>ReleasePeersCore</c> method will not be invoked | ||
after <c>Dispose()</c> has been invoked.</para> | ||
</block> | ||
</remarks> | ||
</member> | ||
</docs> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
|
||
namespace Java.Interop { | ||
|
||
public enum JavaScopeCleanup { | ||
RegisterWithManager, | ||
Dispose, | ||
Release, | ||
} | ||
|
||
public ref struct JavaScope { | ||
|
||
JavaScopeCleanup? cleanup; | ||
PeerableCollection? scope; | ||
|
||
public JavaScope (JavaScopeCleanup cleanup) | ||
{ | ||
this.cleanup = cleanup; | ||
scope = JniEnvironment.CurrentInfo.BeginScope (cleanup); | ||
} | ||
|
||
public void Dispose () | ||
{ | ||
if (cleanup == null || scope == null) { | ||
return; | ||
} | ||
List<Exception>? exceptions = null; | ||
switch (cleanup) { | ||
case JavaScopeCleanup.Dispose: | ||
// Need to iterate over a copy of `scope`, as `p.Dispose()` will modify `scope` | ||
var copy = new IJavaPeerable [scope.Count]; | ||
scope.CopyTo (copy, 0); | ||
foreach (var p in copy) { | ||
try { | ||
p.Dispose (); | ||
} | ||
catch (Exception e) { | ||
exceptions = exceptions ?? new List<Exception>(); | ||
exceptions.Add (e); | ||
Trace.WriteLine (e); | ||
} | ||
} | ||
break; | ||
} | ||
JniEnvironment.CurrentInfo.EndScope (scope); | ||
scope.Clear (); | ||
scope = null; | ||
if (exceptions != null) { | ||
throw new AggregateException (exceptions); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
a675a87
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JavaScopeCleanup.RegisterWithManager
should probably beJavaScopeCleanup.LeaveWithManager
, as object construction must register with the manager; whatJavaScopeCleanup
details is wha to do with the registrations created within theJavaScope
..RegisterWithManager
doesn't really provide clarity here.