Skip to content

Implicit GC.KeepAlive in classes with finalizers #103522

@EgorBo

Description

@EgorBo

Normally, the fact that GC is precise has no visible impact on user's code in terms of correctness, however, there are certain scenarios where developer might be expected to keep an object alive by rooting it or using GC.KeepAlive and may hit rarely reproducible bugs otherwise. It typically happens with finalizers, for example:

public class MyClass
{
    public IntPtr Handle;

    public void DoWork()
    {
        // E.g. some pinvoke
        Native.DoSomeWork(Handle);
    }

    ~MyClass()
    {
        Native.DestroyHandle(Handle); // can be invoked before DoSomeWork finishes!
    }
}

// Execution example:
new MyClass().DoWork();

Here it's not obvious that DestroyHandle(Handle) can be legally invoked while DoSomeWork(Handle) is still being executed (or even not started yet!), it happens because of the precise GC (FullOpts/Tier1) that may mark "this" object as dead after Handle field is accessed - DoWork method doesn't even have to be inlined. The only way to prevent this behavior is to add GC.KeepAlive(this); after DoSomeWork(Handle);

Numerous people were not aware about this, IMO, not obvious behavior and suspected their code is subject to potential bugs (typically, "use after free"-like). Afair, @rickbrew had to insert a lot of such GC.KeepAlive(this); in Paint.NET application, also, @kekekeks suspects they might have potential bugs in Avalonia due to this behavior.

Here is the minimal repro: https://gist.github.com/EgorBo/f30e159c634f40b82b4edc78cf373466

Proposed solution

We can artificially extend life of this pointer in all instance methods of classes with finalizers by, effectively, emitting GC.KeepAlive(this) before all returns in instance methods to significantly reduce number of potential issues in users code at a small cost of making some objects live longer than needed. It's not supposed to be a silver bullet as there are still cases where GC.KeepAlive(obj) (or rooting an object) may still be required. It might be implemented on Roslyn side (so other runtimes with precise GC won't step on this too) or in JIT's liveness analysis.

cc @jkotas

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions