Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[linker] Add AddKeepAlivesStep (#5278)
Context: dotnet/java-interop#719 Context: dotnet/java-interop@1f21f38 Context: https://docs.microsoft.com/archive/blogs/cbrumme/lifetime-gc-keepalive-handle-recycling We discovered the cause of a latent potential crash within Xamarin.Android apps: JNI DETECTED ERROR IN APPLICATION: use of deleted global reference 0x3d86 … The cause of the "use of deleted global reference" was that the GC collected an instance after it was provided to Java, but before Java could keep the instance alive in a manner which would cause our GC bridge to keep it alive, akin to: CallIntoJava (new JavaLangObjectSubclass ().Handle); In the above example code, if the `JavaLangObjectSubclass` instance is collected by the GC *after* the `.Handle` property is accessed but *before* `CallIntoJava()` is executed, then the `.Handle` value will refer to a "deleted global reference" and cause the app crash. The appropriate fix is to ensure that the GC *won't* collect it, usually by calling [`GC.KeepAlive()`][0]: var value = new JavaLangObjectSubclass (); CallIntoJava (value.Handle); GC.KeepAlive (value); An important aspect of the problem is that much of the relevant code introducing this scenario is within *binding assemblies*: partial class Activity { public virtual unsafe Android.App.PendingIntent? CreatePendingResult ( int requestCode, Android.Content.Intent data, [global::Android.Runtime.GeneratedEnum] Android.App.PendingIntentFlags flags) { const string __id = "createPendingResult.(ILandroid/content/Intent;I)Landroid/app/PendingIntent;"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [3]; __args [0] = new JniArgumentValue (requestCode); __args [1] = new JniArgumentValue ((data == null) ? IntPtr.Zero : ((global::Java.Lang.Object) data).Handle); __args [2] = new JniArgumentValue ((int) flags); var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, __args); return global::Java.Lang.Object.GetObject<Android.App.PendingIntent> (__rm.Handle, JniHandleOwnership.TransferLocalRef); } finally { } } } Here, the issue is that `data.Handle` is passed into Java code, but it's possible that `data` may be collected *after* `data.Handle` is accessed but before `_members.InstanceMethods.InvokeVirtualObjectMethod()` is invoked. dotnet/java-interop@1f21f38c introduced a `generator` fix for this this problem, inserting an appropriate `GC.KeepAlive()`: partial class Activity { public virtual unsafe Android.App.PendingIntent? CreatePendingResult ( int requestCode, Android.Content.Intent data, [global::Android.Runtime.GeneratedEnum] Android.App.PendingIntentFlags flags) { const string __id = "createPendingResult.(ILandroid/content/Intent;I)Landroid/app/PendingIntent;"; try { JniArgumentValue* __args = stackalloc JniArgumentValue [3]; __args [0] = new JniArgumentValue (requestCode); __args [1] = new JniArgumentValue ((data == null) ? IntPtr.Zero : ((global::Java.Lang.Object) data).Handle); __args [2] = new JniArgumentValue ((int) flags); var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, __args); return global::Java.Lang.Object.GetObject<Android.App.PendingIntent> (__rm.Handle, JniHandleOwnership.TransferLocalRef); } finally { GC.KeepAlive (data); } } } The problem is that a fix within *generator* is meaningless until all relevant binding assemblies are *also* updated: fixing the issue within `Mono.Android.dll` only fixes `Mono.Android.dll`! We don't -- and can't! -- expect the entire ecosystem to rebuild and republish binding assemblies, which means a `generator` fix isn't a complete fix! To help fix the *ecosystem*, update the *linker* to insert appropriate `GC.KeepAlive()` invocations into marshal methods. This allows fixing the "problem domain" -- premature instance collection -- without requiring that the entire ecosystem be rebuilt. Instead, it "merely" requires that all *applications* be rebuilt. Add a new `$(AndroidAddKeepAlives)` MSBuild property which controls whether or not the linker inserts the `GC.KeepAlive()` calls. `$(AndroidAddKeepAlives)` defaults to True for Release configuration apps, and is False by default for "fast deployment" Debug builds. `$(AndroidAddKeepAlives)` can be overridden to explicitly enable or disable the new linker steps. The impact on release build of XA forms template (flyout variety) is cca 137ms on @radekdoulik's machine. * Disabled: 12079 ms LinkAssemblies 1 calls * Enabled: 12216 ms LinkAssemblies 1 calls Co-authored-by: Radek Doulik <[email protected]> [0]: https://docs.microsoft.com/dotnet/api/system.gc.keepalive
- Loading branch information