Skip to content

GC.KeepAlive doesn't keep local delegate alive in mono (Xamarin.Android) #74966

@trampster

Description

@trampster

EDITED by @stephentoub on 1/20/2023:
The issue at this point tracks an analyzer that would flag use of GC.KeepAlive(local) usage in the context of C# iterators, async methods, and anywhere else the C# compiler does a significant method rewrite such that a "local" isn't actually local, e.g. where that local is lifted to a state machine and the use of GC.KeepAlive has little to no impact, e.g.

object o = new object();
yield return o;
GC.KeepAlive(o);

Description

According to Eric Lippert there is no need to pin a local delegate or keep a GCHandle when passing a delegate via PInvoke as long as you use GC.KeepAlive to keep the delegate alive for as long as the native code could call it. https://stackoverflow.com/a/5465380/78561

However while this seems to be true for the dotnet runtime in mono this does not work and we are getting crashes when the callback happens.

We have found that in mono we need to keep a Normal GCHandle to the delegate GC.KeepAlive does not seem to be enough.

This behaviour needs to be consistent across the mono/dotnet runtimes.

This issue was found when using Xamarin.Android

Reproduction Steps

var taskCompletionSource = new TaskCompletionSource();

MyDelegateType myDelegate = () => taskCompletionSource.SetResult(true);

NativeMethods.MyNativeMethod(myDelegate);

await taskCompletionSource.Task;

GC.KeepAlive(myDelegate);

Expected behavior

GC.KeepAlive should prevent the delegate from being collected until the delegate callback has happened.

Actual behavior

Crash, usually with NullReferenceException

Regression?

No response

Known Workarounds

Get a normal GCHandle to the delegate instead of using GC.KeepAlive

var taskCompletionSource = new TaskCompletionSource();

MyDelegateType myDelegate = () => taskCompletionSource.SetResult(true);

var gcHandle = GCHandle.Alloc(myDelegate, GCHandleType.Normal);

NativeMethods.MyNativeMethod(myDelegate);

await taskCompletionSource.Task;

gcHandle.Free();

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Threading.Taskscode-analyzerMarks an issue that suggests a Roslyn analyzerhelp wanted[up-for-grabs] Good issue for external contributors

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions