Skip to content

Commit a675a87

Browse files
committed
[Java.Interop] Add JavaScope?
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
1 parent f4e68b5 commit a675a87

19 files changed

+1217
-239
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?xml version="1.0"?>
2+
<docs>
3+
<member name="T:JniValueManager">
4+
<summary>
5+
Manages the mapping between Java instances and registered managed instances.
6+
</summary>
7+
<remarks>
8+
<para>
9+
<c>JniRuntime.JniValueManager</c> manages a mapping between Java instances,
10+
and <see cref="T:IJavaPeerable" /> instances. A <c>IJavaPeerable</c> is
11+
<i>registered</i> if it has been added to <c>JniRuntime.JniValueManager</c>.
12+
Certain methods only deal with registered instances. Managed-to-Java instance
13+
method invocation does not require the use of registered instances.
14+
Java-to-Managed instance method invocation requires the use of registred
15+
instances; if possible, a managed peer will be implicitly created.
16+
See <see cref="M:GetValue" /> for details.
17+
</para>
18+
<list type="bullet">
19+
<item><term>
20+
Marshaling infrastructure:
21+
<see cref="M:WaitForGCBridgeProcessing" />.
22+
</term></item>
23+
<item><term>
24+
Lifetime management of all registered peers:
25+
<see cref="M:CollectPeers" />,
26+
<see cref="M:DisposePeers" />,
27+
<see cref="M:ReleasePeers" />.
28+
</term></item>
29+
<item><term>
30+
Registration management for individual peers:
31+
<see cref="M:ActivatePeer" />,
32+
<see cref="M:AddPeer" />,
33+
<see cref="M:ConstructPeer" />,
34+
<see cref="M:DisposePeer" />,
35+
<see cref="M:RemovePeer" />.
36+
</term></item>
37+
<item><term>
38+
Java-to-managed marshaling support:
39+
<see cref="M:GetValue{T}" />,
40+
<see cref="M:GetValue" />,
41+
<see cref="M:GetValueMarshaler" />.
42+
</term></item>
43+
<item><term>
44+
Managed-to-Java marshaling support:
45+
<see cref="M:GetValueMarshaler" />.
46+
</term></item>
47+
</list>
48+
<para>
49+
Managed-to-Java marshaling support is handled via explicit usage,
50+
or "generically" via <see cref="T:JniValueMarshaler" /> and
51+
<see cref="T:JniValueMarshaler{T}" />.
52+
</para>
53+
<block subset="none" type="overrides">
54+
All subclasses must be thread safe.
55+
</block>
56+
</remarks>
57+
<threadsafe>This type is thread safe.</threadsafe>
58+
</member>
59+
<!--
60+
Marshaling Infrastructure
61+
-->
62+
<member name="M:WaitForGCBridgeProcessing">
63+
<summary>
64+
Infrastructure. Called during Java-to-managed transitions.
65+
</summary>
66+
</member>
67+
<!--
68+
Global peer lifetime maangement
69+
-->
70+
<member name="P:CanCollectPeers">
71+
<summary>
72+
Whether or not <see cref="M:CollectPeers" /> is supported.
73+
</summary>
74+
<remarks>
75+
<para>
76+
When <c>CanCollectPeers</c> returns <c>false</c>, calls to
77+
<see cref="M:CollectPeers" /> will throw
78+
<see cref="T:System.NotSupportedException" />.
79+
</para>
80+
</remarks>
81+
</member>
82+
<member name="M:CollectPeers">
83+
<summary>
84+
Garbage collects all peer instances.
85+
</summary>
86+
<remarks>
87+
<para>
88+
When <c>CanCollectPeers</c> returns <c>false</c>, calls to
89+
<see cref="M:CollectPeers" /> will throw
90+
<see cref="T:System.NotSupportedException" />.
91+
</para>
92+
</remarks>
93+
<exception cref="T:System.NotSupportedException">
94+
Garbage collection of peers is not supported.
95+
</exception>
96+
<exception cref="T:System.ObjectDisposedException">
97+
<see cref="M:Dispose()" /> has previously been invoked.
98+
</exception>
99+
</member>
100+
<member name="M:CollectPeersCore">
101+
<summary>
102+
Garbage collects all peer instances.
103+
</summary>
104+
<remarks>
105+
<block subset="none" type="overrides">
106+
<para>The <c>CollectPeersCore</c> method will not be invoked
107+
after <c>Dispose()</c> has been invoked.</para>
108+
</block>
109+
</remarks>
110+
<exception cref="T:System.NotSupportedException">
111+
Garbage collection of peers is not supported.
112+
</exception>
113+
</member>
114+
<member name="M:DisposePeers">
115+
<summary>
116+
Dispose of all registered peer instances.
117+
</summary>
118+
<remarks>
119+
<para>
120+
Calls <see cref="M:System.IDisposable.Dispose" /> on all peer instances.
121+
</para>
122+
</remarks>
123+
<exception cref="T:System.AggregateException">
124+
Contains all exceptions thrown by registered instances when calling
125+
<see cref="M:System.IDisposable.Dispose" />.
126+
</exception>
127+
<exception cref="T:System.ObjectDisposedException">
128+
<see cref="M:Dispose" /> has previously been invoked.
129+
</exception>
130+
</member>
131+
<member name="M:DisposePeersCore">
132+
<summary>
133+
Dispose of all registered peer instances.
134+
</summary>
135+
<remarks>
136+
<block subset="none" type="overrides">
137+
<para>
138+
The <c>DisposePeersCore</c> method will not be invoked
139+
after <see cref="M:Dispose" /> has been invoked.</para>
140+
<para>Inheritors should invoke <see cref="M:System.IDisposable.Dispose" />
141+
on all peer instances. Should any peer throw from the <c>Dispose</c>
142+
invocation, then <c>DisposePeersCore</c> should capture all thrown
143+
exceptions and re-raise them within a <see cref="T:System.AggregateException" />.
144+
</para>
145+
</block>
146+
</remarks>
147+
<exception cref="T:System.AggregateException">
148+
Contains all exceptions thrown by <see cref="M:System.IDisposable.Dispose" />.
149+
</exception>
150+
</member>
151+
<member name="M:ReleasePeers">
152+
<summary>
153+
Release all registered peer instances.
154+
</summary>
155+
<remarks>
156+
<para>
157+
The <c>JniValueManager</c> unregisters all peers.
158+
Methods such as <see cref="M:PeekPeer" /> will not find return any peers.
159+
</para>
160+
<para>
161+
Peer values may still be used, even if not referenced by a <c>JniValueManager</c>.
162+
</para>
163+
</remarks>
164+
<exception cref="T:System.ObjectDisposedException">
165+
<see cref="M:Dispose" /> has previously been invoked.
166+
</exception>
167+
</member>
168+
<member name="M:ReleasePeersCore">
169+
<summary>
170+
Release all registered peer instances.
171+
</summary>
172+
<remarks>
173+
<block subset="none" type="overrides">
174+
<para>The <c>ReleasePeersCore</c> method will not be invoked
175+
after <c>Dispose()</c> has been invoked.</para>
176+
</block>
177+
</remarks>
178+
</member>
179+
<!--
180+
Lifetime management for individual peers
181+
-->
182+
<member name="M:AddPeer">
183+
<summary>
184+
Register a managed peer.
185+
</summary>
186+
<remarks>
187+
<block subset="none" type="overrides">
188+
<para>The <c>ReleasePeersCore</c> method will not be invoked
189+
after <c>Dispose()</c> has been invoked.</para>
190+
</block>
191+
</remarks>
192+
</member>
193+
</docs>

src/Java.Interop/GlobalSuppressions.cs

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
[assembly: SuppressMessage ("Design", "CA1024:Use properties where appropriate", Justification = "<Pending>", Scope = "member", Target = "~M:Java.Interop.JniRuntime.GetRegisteredRuntimes()")]
1616

17+
[assembly: SuppressMessage ("Design", "CA1031:Do not catch general exception types", Justification = "Excceptions are bundled into an AggregateException and rethrown", Scope = "type", Target = "~M:Java.Interop.JavaScope.Dispose")]
18+
1719
[assembly: SuppressMessage ("Design", "CA1032:Implement standard exception constructors", Justification = "System.Runtime.Serialization.SerializationInfo doesn't exist in our targeted PCL profile, so we can't provide the (SerializationInfo, StreamingContext) constructor.", Scope = "type", Target = "~T:Java.Interop.JavaProxyThrowable")]
1820
[assembly: SuppressMessage ("Design", "CA1032:Implement standard exception constructors", Justification = "System.Runtime.Serialization.SerializationInfo doesn't exist in our targeted PCL profile, so we can't provide the (SerializationInfo, StreamingContext) constructor.", Scope = "type", Target = "~T:Java.Interop.JniLocationException")]
1921

src/Java.Interop/Java.Interop/JavaProxyObject.cs

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22

33
using System;
4+
using System.Collections.Generic;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Runtime.CompilerServices;
67

@@ -28,10 +29,8 @@ public override JniPeerMembers JniPeerMembers {
2829
}
2930
}
3031

31-
JavaProxyObject (object value)
32+
internal JavaProxyObject (object value)
3233
{
33-
if (value == null)
34-
throw new ArgumentNullException (nameof (value));
3534
Value = value;
3635
}
3736

@@ -56,18 +55,11 @@ public override bool Equals (object? obj)
5655
return Value.ToString ();
5756
}
5857

59-
[return: NotNullIfNotNull ("object")]
60-
public static JavaProxyObject? GetProxy (object value)
58+
protected override void Dispose (bool disposing)
6159
{
62-
if (value == null)
63-
return null;
64-
65-
lock (CachedValues) {
66-
if (CachedValues.TryGetValue (value, out var proxy))
67-
return proxy;
68-
proxy = new JavaProxyObject (value);
69-
CachedValues.Add (value, proxy);
70-
return proxy;
60+
base.Dispose (disposing);
61+
if (disposing) {
62+
CachedValues.Remove (Value);
7163
}
7264
}
7365

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
5+
namespace Java.Interop {
6+
7+
public enum JavaScopeCleanup {
8+
RegisterWithManager,
9+
Dispose,
10+
Release,
11+
}
12+
13+
public ref struct JavaScope {
14+
15+
JavaScopeCleanup? cleanup;
16+
PeerableCollection? scope;
17+
18+
public JavaScope (JavaScopeCleanup cleanup)
19+
{
20+
this.cleanup = cleanup;
21+
scope = JniEnvironment.CurrentInfo.BeginScope (cleanup);
22+
}
23+
24+
public void Dispose ()
25+
{
26+
if (cleanup == null || scope == null) {
27+
return;
28+
}
29+
List<Exception>? exceptions = null;
30+
switch (cleanup) {
31+
case JavaScopeCleanup.Dispose:
32+
// Need to iterate over a copy of `scope`, as `p.Dispose()` will modify `scope`
33+
var copy = new IJavaPeerable [scope.Count];
34+
scope.CopyTo (copy, 0);
35+
foreach (var p in copy) {
36+
try {
37+
p.Dispose ();
38+
}
39+
catch (Exception e) {
40+
exceptions = exceptions ?? new List<Exception>();
41+
exceptions.Add (e);
42+
Trace.WriteLine (e);
43+
}
44+
}
45+
break;
46+
}
47+
JniEnvironment.CurrentInfo.EndScope (scope);
48+
scope.Clear ();
49+
scope = null;
50+
if (exceptions != null) {
51+
throw new AggregateException (exceptions);
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)