-
Notifications
You must be signed in to change notification settings - Fork 568
Expand file tree
/
Copy pathBridgeProcessing.cs
More file actions
365 lines (299 loc) · 12.2 KB
/
BridgeProcessing.cs
File metadata and controls
365 lines (299 loc) · 12.2 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
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Java;
using Android.Runtime;
using Java.Interop;
namespace Microsoft.Android.Runtime;
/// <summary>
/// Handles GC bridge processing between managed and Java objects.
/// This is the C# port of the native bridge-processing.cc logic.
/// </summary>
unsafe class BridgeProcessing
{
readonly MarkCrossReferencesArgs* crossRefs;
// Temporary peers created for empty SCCs
readonly Dictionary<nuint, JniObjectReference> temporaryPeers = new ();
// Cached Java class and method references
static JniType? s_GCUserPeerClass;
static JniMethodInfo? s_GCUserPeerCtor;
static readonly BridgeProcessingLogger? s_logger;
static BridgeProcessing ()
{
s_GCUserPeerClass = new JniType ("mono/android/GCUserPeer");
s_GCUserPeerCtor = s_GCUserPeerClass.GetConstructor ("()V");
if (Logger.LogGC || Logger.LogGlobalRef) {
s_logger = new BridgeProcessingLogger ();
}
}
public BridgeProcessing (MarkCrossReferencesArgs* args)
{
if (args == null) {
throw new ArgumentNullException (nameof (args), "Cross references argument is a NULL pointer");
}
if (args->ComponentCount > 0 && args->Components == null) {
throw new InvalidOperationException ("Components member of the cross references arguments structure is NULL");
}
if (args->CrossReferenceCount > 0 && args->CrossReferences == null) {
throw new InvalidOperationException ("CrossReferences member of the cross references arguments structure is NULL");
}
crossRefs = args;
}
/// <summary>
/// Main processing method - equivalent to BridgeProcessingShared::process()
/// </summary>
public void Process ()
{
PrepareForJavaCollection ();
JavaGCTrigger.Trigger ();
CleanupAfterJavaCollection ();
s_logger?.LogGcSummary (crossRefs);
}
/// <summary>
/// Prepare objects for Java GC by setting up cross-references and converting to weak refs
/// </summary>
void PrepareForJavaCollection ()
{
// Before looking at xrefs, scan the SCCs. During collection, an SCC has to behave like a
// single object. If the number of objects in the SCC is anything other than 1, the SCC
// must be doctored to mimic that one-object nature.
for (nuint i = 0; i < crossRefs->ComponentCount; i++) {
ref StronglyConnectedComponent scc = ref crossRefs->Components[i];
PrepareSccForJavaCollection (i, ref scc);
}
// Add the cross scc refs
for (nuint i = 0; i < crossRefs->CrossReferenceCount; i++) {
ref ComponentCrossReference xref = ref crossRefs->CrossReferences[i];
AddCrossReference (xref.SourceGroupIndex, xref.DestinationGroupIndex);
}
// With cross references processed, the temporary peer list can be released
foreach (var (_, peer) in temporaryPeers) {
var reference = peer;
JniObjectReference.Dispose (ref reference);
}
// Switch global to weak references
for (nuint i = 0; i < crossRefs->ComponentCount; i++) {
ref StronglyConnectedComponent scc = ref crossRefs->Components[i];
for (nuint j = 0; j < scc.Count; j++) {
HandleContext* context = (HandleContext*)scc.Contexts[j];
Debug.Assert (context != null, "Context must not be null");
TakeWeakGlobalRef (context);
}
}
}
void PrepareSccForJavaCollection (nuint sccIndex, ref StronglyConnectedComponent scc)
{
// Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one
if (scc.Count == 0) {
var newObject = s_GCUserPeerClass!.NewObject (s_GCUserPeerCtor!, null);
temporaryPeers[sccIndex] = newObject;
return;
}
// Count == 1 case: The SCC contains a single object, there is no need to do anything special.
if (scc.Count == 1) {
return;
}
// Count > 1 case: The SCC contains many objects which must be collected as one.
// Solution: Make all objects within the SCC directly or indirectly reference each other
AddCircularReferences (ref scc);
}
void AddCircularReferences (ref StronglyConnectedComponent scc)
{
nuint prevIndex = scc.Count - 1;
for (nuint nextIndex = 0; nextIndex < scc.Count; nextIndex++) {
HandleContext* prev = (HandleContext*)scc.Contexts[prevIndex];
HandleContext* next = (HandleContext*)scc.Contexts[nextIndex];
Debug.Assert (prev != null && prev->ControlBlock != null, "Previous context or control block is null");
Debug.Assert (next != null && next->ControlBlock != null, "Next context or control block is null");
var prevRef = new JniObjectReference (prev->ControlBlock->Handle, JniObjectReferenceType.Global);
var nextRef = new JniObjectReference (next->ControlBlock->Handle, JniObjectReferenceType.Global);
bool referenceAdded = AddReference (prevRef, nextRef);
if (!referenceAdded) {
throw new InvalidOperationException ("Failed to add reference between objects in a strongly connected component");
}
prev->ControlBlock->RefsAdded = 1;
prevIndex = nextIndex;
}
}
void AddCrossReference (nuint sourceIndex, nuint destIndex)
{
var (fromRef, fromContextPtr) = SelectCrossReferenceTarget (sourceIndex);
var (toRef, _) = SelectCrossReferenceTarget (destIndex);
if (AddReference (fromRef, toRef) && fromContextPtr != IntPtr.Zero) {
HandleContext* fromContext = (HandleContext*)fromContextPtr;
fromContext->ControlBlock->RefsAdded = 1;
}
}
/// <summary>
/// Selects the target for a cross-reference from an SCC.
/// Returns the JNI reference and the handle context pointer (IntPtr.Zero for temporary peers).
/// </summary>
(JniObjectReference Reference, IntPtr ContextPtr) SelectCrossReferenceTarget (nuint sccIndex)
{
ref StronglyConnectedComponent scc = ref crossRefs->Components[sccIndex];
if (scc.Count == 0) {
if (!temporaryPeers.TryGetValue (sccIndex, out var tempPeer)) {
throw new InvalidOperationException ("Temporary peer must be found in the map");
}
return (tempPeer, IntPtr.Zero);
}
HandleContext* context = (HandleContext*)scc.Contexts[0];
Debug.Assert (context != null && context->ControlBlock != null, "SCC must have at least one valid context");
var reference = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.Global);
return (reference, (IntPtr)context);
}
bool AddReference (JniObjectReference from, JniObjectReference to)
{
if (!from.IsValid || !to.IsValid) {
return false;
}
// Try the optimized path for GCUserPeerable (NativeAOT)
if (!RuntimeFeature.IsCoreClrRuntime) {
if (JavaGCTrigger.TryAddManagedReference (from, to)) {
return true;
}
}
// Fall back to reflection-based approach
var fromClassRef = JniEnvironment.Types.GetObjectClass (from);
using var fromClass = new JniType (ref fromClassRef, JniObjectReferenceOptions.CopyAndDispose);
JniMethodInfo addMethod;
try {
addMethod = fromClass.GetInstanceMethod ("monodroidAddReference", "(Ljava/lang/Object;)V");
} catch (Java.Lang.NoSuchMethodError) {
s_logger?.LogMissingAddReferencesMethod (fromClass);
return false;
}
JniArgumentValue* args = stackalloc JniArgumentValue[1];
args[0] = new JniArgumentValue (to);
JniEnvironment.InstanceMethods.CallVoidMethod (from, addMethod, args);
return true;
}
void TakeWeakGlobalRef (HandleContext* context)
{
Debug.Assert (context != null && context->ControlBlock != null, "Context or control block is null");
Debug.Assert (context->ControlBlock->HandleType == (int)JniObjectReferenceType.Global, "Expected global reference type for handle");
var handle = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.Global);
s_logger?.LogTakeWeakGlobalRef (handle);
var weak = handle.NewWeakGlobalRef ();
s_logger?.LogWeakGrefNew (handle, weak);
context->ControlBlock->Handle = weak.Handle;
context->ControlBlock->HandleType = (int)JniObjectReferenceType.WeakGlobal;
// Delete the old global ref
s_logger?.LogGrefDelete (handle);
JniObjectReference.Dispose (ref handle);
}
void CleanupAfterJavaCollection ()
{
for (nuint i = 0; i < crossRefs->ComponentCount; i++) {
ref StronglyConnectedComponent scc = ref crossRefs->Components[i];
// Try to switch back to global refs to analyze what stayed alive
for (nuint j = 0; j < scc.Count; j++) {
HandleContext* context = (HandleContext*)scc.Contexts[j];
Debug.Assert (context != null, "Context must not be null");
TakeGlobalRef (context);
ClearReferencesIfNeeded (context);
}
AssertAllCollectedOrAllAlive (ref scc);
}
}
void TakeGlobalRef (HandleContext* context)
{
Debug.Assert (context != null && context->ControlBlock != null, "Context or control block is null");
Debug.Assert (context->ControlBlock->HandleType == (int)JniObjectReferenceType.WeakGlobal, "Expected weak global reference type for handle");
var weak = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.WeakGlobal);
var handle = weak.NewGlobalRef ();
s_logger?.LogWeakToGref (weak, handle);
// The weak reference might have been collected
if (handle.Handle == IntPtr.Zero) {
s_logger?.LogWeakRefCollected (weak);
}
context->ControlBlock->Handle = handle.Handle; // This may be null if collected
context->ControlBlock->HandleType = (int)JniObjectReferenceType.Global;
// Delete the old weak ref
s_logger?.LogWeakRefDelete (weak);
JniObjectReference.Dispose (ref weak);
}
void ClearReferencesIfNeeded (HandleContext* context)
{
if (IsCollected (context)) {
return;
}
Debug.Assert (context->ControlBlock != null, "Control block must not be null");
Debug.Assert (context->ControlBlock->Handle != IntPtr.Zero, "Control block handle must not be null");
Debug.Assert (context->ControlBlock->HandleType == (int)JniObjectReferenceType.Global, "Control block handle type must be global reference");
if (context->ControlBlock->RefsAdded == 0) {
return;
}
var handle = new JniObjectReference (context->ControlBlock->Handle, JniObjectReferenceType.Global);
ClearReferences (handle);
context->ControlBlock->RefsAdded = 0;
}
void ClearReferences (JniObjectReference handle)
{
if (!handle.IsValid) {
return;
}
// Try the optimized path for GCUserPeerable (NativeAOT)
if (!RuntimeFeature.IsCoreClrRuntime) {
if (JavaGCTrigger.TryClearManagedReferences (handle)) {
return;
}
}
// Fall back to reflection-based approach
var javaClassRef = JniEnvironment.Types.GetObjectClass (handle);
using var javaClass = new JniType (ref javaClassRef, JniObjectReferenceOptions.CopyAndDispose);
JniMethodInfo clearMethod;
try {
clearMethod = javaClass.GetInstanceMethod ("monodroidClearReferences", "()V");
} catch (Java.Lang.NoSuchMethodError) {
s_logger?.LogMissingClearReferencesMethod (javaClass);
return;
}
JniEnvironment.InstanceMethods.CallVoidMethod (handle, clearMethod, null);
}
static bool IsCollected (HandleContext* context)
{
Debug.Assert (context != null && context->ControlBlock != null, "Context or control block is null");
return context->ControlBlock->Handle == IntPtr.Zero;
}
void AssertAllCollectedOrAllAlive (ref StronglyConnectedComponent scc)
{
if (scc.Count == 0) {
return;
}
HandleContext* firstContext = (HandleContext*)scc.Contexts[0];
Debug.Assert (firstContext != null, "Context must not be null");
bool isCollected = IsCollected (firstContext);
for (nuint j = 1; j < scc.Count; j++) {
HandleContext* context = (HandleContext*)scc.Contexts[j];
Debug.Assert (context != null, "Context must not be null");
if (IsCollected (context) != isCollected) {
throw new InvalidOperationException ("Cannot have a mix of collected and alive contexts in the SCC");
}
}
}
/// <summary>
/// Internal representation of the JNI object reference control block.
/// This must match the layout of Java.Interop.JniObjectReferenceControlBlock and the native struct.
/// </summary>
[StructLayout (LayoutKind.Sequential)]
internal struct JniObjectReferenceControlBlock
{
public IntPtr Handle;
public int HandleType;
public IntPtr WeakHandle;
public int RefsAdded;
}
/// <summary>
/// Internal representation of the handle context passed from the GC bridge.
/// This must match the layout of the native HandleContext struct.
/// </summary>
[StructLayout (LayoutKind.Sequential)]
internal struct HandleContext
{
public int IdentityHashCode;
public JniObjectReferenceControlBlock* ControlBlock;
}
}