-
Notifications
You must be signed in to change notification settings - Fork 566
Expand file tree
/
Copy pathManagedValueManager.cs
More file actions
517 lines (432 loc) · 16 KB
/
ManagedValueManager.cs
File metadata and controls
517 lines (432 loc) · 16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
// Originally from: https://github.com/dotnet/java-interop/blob/9b1d8781e8e322849d05efac32119c913b21c192/src/Java.Runtime.Environment/Java.Interop/ManagedValueManager.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Java;
using System.Threading;
using System.Threading.Tasks;
using Android.Runtime;
using Java.Interop;
namespace Microsoft.Android.Runtime;
class ManagedValueManager : JniRuntime.JniValueManager
{
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
readonly Lock _registeredInstancesLock = new ();
readonly Dictionary<int, List<ReferenceTrackingHandle>> _registeredInstances = new ();
readonly ConcurrentQueue<IntPtr> _collectedContexts = new ();
bool _disposed;
static Lazy<ManagedValueManager> s_instance = new (() => new ManagedValueManager ());
public static ManagedValueManager GetOrCreateInstance () => s_instance.Value;
unsafe ManagedValueManager ()
{
// There can only be one instance of ManagedValueManager because we can call JavaMarshal.Initialize only once.
var mark_cross_references_ftn = RuntimeNativeMethods.clr_initialize_gc_bridge (&BridgeProcessingStarted, &BridgeProcessingFinished);
JavaMarshal.Initialize (mark_cross_references_ftn);
}
protected override void Dispose (bool disposing)
{
_disposed = true;
base.Dispose (disposing);
}
void ThrowIfDisposed ()
{
if (_disposed)
throw new ObjectDisposedException (nameof (ManagedValueManager));
}
public override void WaitForGCBridgeProcessing ()
{
}
public unsafe override void CollectPeers ()
{
ThrowIfDisposed ();
while (_collectedContexts.TryDequeue (out IntPtr contextPtr)) {
Debug.Assert (contextPtr != IntPtr.Zero, "_collectedContexts should not contain null pointers.");
HandleContext* context = (HandleContext*)contextPtr;
lock (_registeredInstancesLock) {
Remove (context);
}
HandleContext.Free (ref context);
}
void Remove (HandleContext* context)
{
int key = context->PeerIdentityHashCode;
if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers))
return;
for (int i = peers.Count - 1; i >= 0; i--) {
var peer = peers [i];
if (peer.BelongsToContext (context)) {
peers.RemoveAt (i);
}
}
if (peers.Count == 0) {
_registeredInstances.Remove (key);
}
}
}
public override void AddPeer (IJavaPeerable value)
{
ThrowIfDisposed ();
var r = value.PeerReference;
if (!r.IsValid)
throw new ObjectDisposedException (value.GetType ().FullName);
if (r.Type != JniObjectReferenceType.Global) {
value.SetPeerReference (r.NewGlobalRef ());
JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose);
}
int key = value.JniIdentityHashCode;
lock (_registeredInstancesLock) {
List<ReferenceTrackingHandle>? peers;
if (!_registeredInstances.TryGetValue (key, out peers)) {
peers = [new ReferenceTrackingHandle (value)];
_registeredInstances.Add (key, peers);
return;
}
for (int i = peers.Count - 1; i >= 0; i--) {
ReferenceTrackingHandle peer = peers [i];
if (peer.Target is not IJavaPeerable target)
continue;
if (!JniEnvironment.Types.IsSameObject (target.PeerReference, value.PeerReference))
continue;
if (target.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable)) {
peer.Dispose ();
peers [i] = new ReferenceTrackingHandle (value);
} else {
WarnNotReplacing (key, value, target);
}
GC.KeepAlive (target);
return;
}
peers.Add (new ReferenceTrackingHandle (value));
}
}
void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue)
{
Runtime.ObjectReferenceManager.WriteGlobalReferenceLine (
"Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " +
"keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.",
ignoreValue.PeerReference.ToString (),
key.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture),
ignoreValue.GetType ().FullName,
JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference),
keepValue.PeerReference.ToString (),
RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture),
keepValue.GetType ().FullName,
JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference));
}
public override IJavaPeerable? PeekPeer (JniObjectReference reference)
{
ThrowIfDisposed ();
if (!reference.IsValid)
return null;
int key = GetJniIdentityHashCode (reference);
lock (_registeredInstancesLock) {
if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers))
return null;
for (int i = peers.Count - 1; i >= 0; i--) {
if (peers [i].Target is IJavaPeerable peer
&& JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
{
return peer;
}
}
if (peers.Count == 0)
_registeredInstances.Remove (key);
}
return null;
}
public override void RemovePeer (IJavaPeerable value)
{
ThrowIfDisposed ();
// Remove any collected contexts before modifying _registeredInstances
CollectPeers ();
if (value == null)
throw new ArgumentNullException (nameof (value));
lock (_registeredInstancesLock) {
int key = value.JniIdentityHashCode;
if (!_registeredInstances.TryGetValue (key, out List<ReferenceTrackingHandle>? peers))
return;
for (int i = peers.Count - 1; i >= 0; i--) {
ReferenceTrackingHandle peer = peers [i];
IJavaPeerable? target = peer.Target;
if (ReferenceEquals (value, target)) {
peers.RemoveAt (i);
peer.Dispose ();
}
GC.KeepAlive (target);
}
if (peers.Count == 0)
_registeredInstances.Remove (key);
}
}
public override void FinalizePeer (IJavaPeerable value)
{
var h = value.PeerReference;
var o = Runtime.ObjectReferenceManager;
// MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment
// and the JniEnvironment's corresponding thread; it's a thread-local value.
// Accessing SafeHandle.ReferenceType won't kill anything (so far...), but
// instead it always returns JniReferenceType.Invalid.
if (!h.IsValid || h.Type == JniObjectReferenceType.Local) {
if (o.LogGlobalReferenceMessages) {
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
h.ToString (),
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
value.GetType ().ToString ());
}
RemovePeer (value);
value.SetPeerReference (new JniObjectReference ());
value.Finalized ();
return;
}
RemovePeer (value);
if (o.LogGlobalReferenceMessages) {
o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
h.ToString (),
value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
value.GetType ().ToString ());
}
value.SetPeerReference (new JniObjectReference ());
JniObjectReference.Dispose (ref h);
value.Finalized ();
}
public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
{
try {
ActivateViaReflection (reference, cinfo, argumentValues);
} catch (Exception e) {
var m = string.Format (
CultureInfo.InvariantCulture,
"Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.",
reference,
GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture),
JniEnvironment.Types.GetJniTypeNameFromInstance (reference),
cinfo.DeclaringType?.FullName);
Debug.WriteLine (m);
throw new NotSupportedException (m, e);
}
}
void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
{
var declType = GetDeclaringType (cinfo);
#pragma warning disable IL2072
var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType);
#pragma warning restore IL2072
self.SetPeerReference (reference);
cinfo.Invoke (self, argumentValues);
[UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷♂️")]
[return: DynamicallyAccessedMembers (Constructors)]
Type GetDeclaringType (ConstructorInfo cinfo) =>
cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!");
}
public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
{
ThrowIfDisposed ();
// Remove any collected contexts before iterating over all the registered instances
CollectPeers ();
lock (_registeredInstancesLock) {
var peers = new List<JniSurfacedPeerInfo> (_registeredInstances.Count);
foreach (var (identityHashCode, referenceTrackingHandles) in _registeredInstances) {
foreach (var peer in referenceTrackingHandles) {
if (peer.Target is IJavaPeerable target) {
peers.Add (new JniSurfacedPeerInfo (identityHashCode, new WeakReference<IJavaPeerable> (target)));
}
}
}
return peers;
}
}
unsafe struct ReferenceTrackingHandle : IDisposable
{
WeakReference<IJavaPeerable?> _weakReference;
HandleContext* _context;
public bool BelongsToContext (HandleContext* context)
=> _context == context;
public ReferenceTrackingHandle (IJavaPeerable peer)
{
_context = HandleContext.Alloc (peer);
_weakReference = new (peer);
}
public IJavaPeerable? Target
=> _weakReference.TryGetTarget (out var target) ? target : null;
public void Dispose ()
{
if (_context == null)
return;
IJavaPeerable? target = Target;
GCHandle handle = _context->AssociatedGCHandle;
HandleContext.Free (ref _context);
_weakReference.SetTarget (null);
if (handle.IsAllocated) {
handle.Free ();
}
// Make sure the target is not collected before we finish disposing
GC.KeepAlive (target);
}
}
[StructLayout (LayoutKind.Sequential)]
unsafe struct HandleContext
{
static readonly nuint Size = (nuint)Marshal.SizeOf<HandleContext> ();
int _identityHashCode;
IntPtr _controlBlock;
IntPtr _gcHandle; // GCHandle stored as IntPtr to keep blittable layout
public int PeerIdentityHashCode => _identityHashCode;
public bool IsCollected
{
get
{
if (_controlBlock == IntPtr.Zero)
throw new InvalidOperationException ("HandleContext control block is not initialized.");
return ((JniObjectReferenceControlBlock*) _controlBlock)->handle == IntPtr.Zero;
}
}
// This is an internal mirror of the Java.Interop.JniObjectReferenceControlBlock
private struct JniObjectReferenceControlBlock
{
public IntPtr handle;
public int handle_type;
public IntPtr weak_handle;
public int refs_added;
}
public GCHandle AssociatedGCHandle => GCHandle.FromIntPtr (_gcHandle);
#if DEBUG
static readonly HashSet<IntPtr> s_knownContexts = new ();
public static unsafe void EnsureAllContextsAreOurs (MarkCrossReferencesArgs* mcr)
{
lock (s_knownContexts) {
for (nuint i = 0; i < mcr->ComponentCount; i++) {
StronglyConnectedComponent component = mcr->Components [i];
for (nuint j = 0; j < component.Count; j++) {
if (!s_knownContexts.Contains ((IntPtr)component.Contexts [j])) {
throw new InvalidOperationException ("Unknown reference tracking handle.");
}
}
}
}
}
#endif
public static HandleContext* Alloc (IJavaPeerable peer)
{
var context = (HandleContext*) NativeMemory.AllocZeroed (1, Size);
if (context == null) {
throw new OutOfMemoryException ("Failed to allocate memory for HandleContext.");
}
context->_identityHashCode = peer.JniIdentityHashCode;
context->_controlBlock = peer.JniObjectReferenceControlBlock;
GCHandle handle = JavaMarshal.CreateReferenceTrackingHandle (peer, context);
context->_gcHandle = GCHandle.ToIntPtr (handle);
#if DEBUG
lock (s_knownContexts) {
s_knownContexts.Add ((IntPtr)context);
}
#endif
return context;
}
public static void Free (ref HandleContext* context)
{
if (context == null) {
return;
}
#if DEBUG
lock (s_knownContexts) {
s_knownContexts.Remove ((IntPtr)context);
}
#endif
NativeMemory.Free (context);
context = null;
}
}
[UnmanagedCallersOnly]
static unsafe void BridgeProcessingStarted (MarkCrossReferencesArgs* mcr)
{
if (mcr == null) {
throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null.");
}
#if DEBUG
HandleContext.EnsureAllContextsAreOurs (mcr);
#endif
}
[UnmanagedCallersOnly]
static unsafe void BridgeProcessingFinished (MarkCrossReferencesArgs* mcr)
{
if (mcr == null) {
throw new ArgumentNullException (nameof (mcr), "MarkCrossReferencesArgs should never be null.");
}
ReadOnlySpan<GCHandle> handlesToFree = ProcessCollectedContexts (mcr);
JavaMarshal.FinishCrossReferenceProcessing (mcr, handlesToFree);
// Schedule cleanup of _registeredInstances on a thread pool thread.
// The bridge thread must not take lock(_registeredInstances) — see deadlock notes.
Task.Run (GetOrCreateInstance ().CollectPeers);
}
static unsafe ReadOnlySpan<GCHandle> ProcessCollectedContexts (MarkCrossReferencesArgs* mcr)
{
List<GCHandle> handlesToFree = [];
ManagedValueManager instance = GetOrCreateInstance ();
for (int i = 0; (nuint)i < mcr->ComponentCount; i++) {
StronglyConnectedComponent component = mcr->Components [i];
for (int j = 0; (nuint)j < component.Count; j++) {
ProcessContext ((HandleContext*)component.Contexts [j]);
}
}
void ProcessContext (HandleContext* context)
{
if (context == null) {
throw new ArgumentNullException (nameof (context), "HandleContext should never be null.");
}
// Ignore contexts which were not collected
if (!context->IsCollected) {
return;
}
GCHandle handle = context->AssociatedGCHandle;
// Note: modifying the _registeredInstances dictionary while processing the collected contexts
// is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free
// them later outside of the bridge processing loop.
instance._collectedContexts.Enqueue ((IntPtr)context);
// important: we must not free the handle before passing it to JavaMarshal.FinishCrossReferenceProcessing
handlesToFree.Add (handle);
}
return CollectionsMarshal.AsSpan (handlesToFree);
}
const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
protected override bool TryConstructPeer (
IJavaPeerable self,
ref JniObjectReference reference,
JniObjectReferenceOptions options,
[DynamicallyAccessedMembers (Constructors)]
Type type)
{
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
if (c != null) {
var args = new object[] {
reference.Handle,
JniHandleOwnership.DoNotTransfer,
};
c.Invoke (self, args);
JniObjectReference.Dispose (ref reference, options);
return true;
}
return base.TryConstructPeer (self, ref reference, options, type);
}
protected override bool TryUnboxPeerObject (IJavaPeerable value, [NotNullWhen (true)] out object? result)
{
var proxy = value as JavaProxyThrowable;
if (proxy != null) {
result = proxy.InnerException;
return true;
}
return base.TryUnboxPeerObject (value, out result);
}
}