Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finalizer is not reliably called regardless of .NET Core or .NET Framework #17463

Closed
MarkMichaelis opened this issue Mar 14, 2020 · 27 comments · Fixed by #24620
Closed

Finalizer is not reliably called regardless of .NET Core or .NET Framework #17463

MarkMichaelis opened this issue Mar 14, 2020 · 27 comments · Fixed by #24620

Comments

@MarkMichaelis
Copy link

MarkMichaelis commented Mar 14, 2020

According to the page:

In .NET Framework applications (but not in .NET Core applications), finalizers are also called when the program exits.

However, given the following code, it appears the finalizer doesn't reliably get called before the program shuts down regardless of whether .NET Core (netcoreapp3.1) or .NET Framework (net46) is used. Adding the Collect() triggers the finalizer in both cases.

    class Program
    {
        static void Main(string[]? args)
        {
            DoSomething();
            // System.GC.Collect();
        }

        static void DoSomething()
        {
            Thing thing = new Thing();
        }
    }

    class Thing
    {
        ~Thing()
        {
            Console.WriteLine("Executing finalizer...");
        }
    }

Admittedly, there is likely some "timing" explanation but seemingly it isn't working as described.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

@BillWagner
Copy link
Member

Thanks @MarkMichaelis

Ping @richlander @karelz Who would be best to help me correctly describe the behavior of finalizers across the .NET CLR implementations?

@karelz
Copy link
Member

karelz commented Mar 17, 2020

@Maoni0 will know probably best ...

@Maoni0
Copy link
Member

Maoni0 commented Mar 17, 2020

there are various timeouts for running finalizers. on .net this was something like 40 seconds total for process shutdown and 2 seconds total for each finalizer. there's also a different timeout for AD shutdown. I'd have to check the src to verify. there's also the distinction between critical finalizers vs non critical finalizers. I can't remember if the timeouts apply to the critical finalizers or not. @jkotas do you remember off the top of your head?

on .net core, based on a cursory look, no one is calling FinalizerThreadWait with an arg which means the timeout is infinite. so looks like no timeouts anymore.

@jkotas
Copy link
Member

jkotas commented Mar 17, 2020

@jkotas
Copy link
Member

jkotas commented Mar 17, 2020

And in even more detail here: https://devblogs.microsoft.com/cbrumme/finalization/

@BillWagner
Copy link
Member

When we updated the C# standard for version 5, we softened the language specific to finalizers as follows:

Prior to an application’s termination, an implementation should make every reasonable effort to call finalizers (§15.13) for all of its objects that have not yet been garbage collected, unless such cleanup has been suppressed (by a call to the library method GC.SuppressFinalize, for example). The implementation should document any conditions under which this behavior cannot be guaranteed.

For this article, that language should be adopted, with the additional details @Maoni0 added (because this article is about the Microsoft implementations).

I've added this to an upcoming sprint to fix.

@MarkMichaelis
Copy link
Author

What I was hoping for was a clearer explanation for why the finalizer was not getting called and, more importantly, what I could do to ensure - under normal shutdown scenarios (e.g. not running under the debugger and stopping the execution) - the finalizer would get called.

@jkotas
Copy link
Member

jkotas commented Mar 18, 2020

If you need to do cleanup during shutdown, the best way to do that is to register for AppDomain.ProcessExit event and do not depend on finalizers.

For any non-trivial real-world app, there is a significant chance that the shutdown finalization gets stuck due to deadlock or aborted due to timeout. For example, Visual Studio does not depend on the shutdown finalization at all (it actually terminates the process after the graceful shutdown cleanup is done but before shutdown finalizers start executing).

@MarkMichaelis
Copy link
Author

Thanks. That is helpful for the application developer. I don't see that as working or a "library" vendor, however. If the code calling my API knows cleanup is necessary, the just call Dispose() and all is fine. The issue here is for when the users of your library forget to or don't realize the Dispose pattern. Finalizers are for this scenario I thought. Also, the code example I have above doesn't have anything exceptional about it. It seems like the most basic of scenarios and yet finalizers are (seemingly?) not called.

@jkotas
Copy link
Member

jkotas commented Mar 18, 2020

The finalizers are never called during shutdown in .NET Core. This is the expected for .NET Core.

For .NET Framework, your repro prints "Executing finalizer..." reliably for me. It is possible that the Console finalizer is running before your finalizer on your machine, and the console is non-functional by the time your finalizer is called. Ie your finalizer is called but you do not see it on the console.

@MSDN-WhiteKnight
Copy link
Contributor

Thanks. That is helpful for the application developer. I don't see that as working or a "library" vendor, however.

But the library could still subscribe to AppDomain.ProcessExit event on first API invocation, maintain the static collection of all disposable objects undisposed by user and dispose them in event handler. Why wouldn't that work for library vendor?

For any non-trivial real-world app, there is a significant chance that the shutdown finalization gets stuck due to deadlock or aborted due to timeout.

Indeed, for OleDB classes, or any STA COM objects in general, shutdown finalizer deadlocks are common in .NET Framework.

@MarkMichaelis
Copy link
Author

I confess it didn't occur to me that the library could also subscribe to the AppDomain.ProcessExit event and I hadn't seen that in guidelines for finalization type scenarios before. I didn't see them outlined in the finalization docs either. I'll go ahead and draft something and make a recommendation for the documentation for your review. Thanks for the insights!

@BillWagner BillWagner self-assigned this Mar 30, 2020
@BillWagner
Copy link
Member

@MarkMichaelis

I'll go ahead and draft something and make a recommendation for the documentation for your review.

Are you interested and have time to make the draft? If not, I'll start on this tomorrow, and ping you for review.

@MarkMichaelis
Copy link
Author

@BillWagner

Sorry... lame. Distracted with some other writing activities that you are aware of. :)

I can take a look at this in a couple of weeks if that works? I understand if it is more critical and you want to do it sooner.

Mark

@BillWagner
Copy link
Member

@MarkMichaelis Thanks. I'm excited to see the results of your other writing. :)

This can wait a couple weeks. I'll work on the other items I have.

@BillWagner BillWagner modified the milestones: April 2020, May 2020 Apr 27, 2020
@BillWagner
Copy link
Member

@MarkMichaelis Are you going to have time to work on this? If not, I'll unassign and more into our backlog.

@BillWagner BillWagner modified the milestones: January 2021, February 2021 Feb 2, 2021
@MarkMichaelis
Copy link
Author

@BillWagner Sure... This month.

@MarkMichaelis
Copy link
Author

Sorry @BillWagner... I'm travelling for the remainder of the month and can't get this done before I leave. I have scheduled the time in for next month so that it won't happen again. :(

@BillWagner BillWagner modified the milestones: February 2021, March 2021 Mar 5, 2021
@BillWagner BillWagner modified the milestones: March 2021, April 2021 Mar 29, 2021
@MarkMichaelis
Copy link
Author

MarkMichaelis commented Apr 4, 2021

@BillWagner - I am actively working on this but I need to check with you on how you want it addressed.

Update text below to indicate that is it not reliably called (since it is true both with .NET Framework and .NET Core there is no point in mentioning either).
Original Text:

In .NET Framework applications (but not in .NET Core applications), finalizers are also called when the program exits.

Becomes:

Finalizers are not reliably called when the program exits. To achieve this, you need to register the cleanup in a subscription to AppDomain.CurrentDomain.ProcessExit.

Given the complete finalization example I mentioned earlier in the thread, should I include the example on this page or should it appear elsewhere?

@MSDN-WhiteKnight
Copy link
Contributor

since it is true both with .NET Framework and .NET Core there is no point in mentioning either.

I think there's a point, per above:

For any non-trivial real-world app, there is a significant chance that the shutdown finalization gets stuck due to deadlock or aborted due to timeout.
Indeed, for OleDB classes, or any STA COM objects in general, shutdown finalizer deadlocks are common in .NET Framework.

People would need to know why these deadlocks are happening. Docs should tell that .NET Framework attempts to call finalizers on shutdown, but timeouts if they take too long (except for CriticalFinalizerObject's which don't timeout); and on .NET Core / .NET 5 it does not attempt it. But the exact details should probably be checked with CLR devs. Bad thing that all important info about finalizers seems to be on blogs which are gone now.

@IEvangelist
Copy link
Member

Hi friends, just circling around on this open issue - is this something that you'll be able to get to @MarkMichaelis? If not, and you have code with a general description - I'd be happy to make it happen, just let me know.

@BillWagner
Copy link
Member

See dotnet/csharpstandard#309 for the updated text in the C# standard (draft for version 6)

@BillWagner BillWagner self-assigned this Jun 1, 2021
@BillWagner BillWagner modified the milestones: April 2021, June 2021 Jun 1, 2021
BillWagner added a commit to BillWagner/docs that referenced this issue Jun 9, 2021
Fixes dotnet#17463

This PR does the following:

- Uses the language from the C# standard to define if finalizers are called.
- copy edit the article. Mostly removing any use of "destructor" which is no longer the preferred term.
- Update samples so they don't show the practice of calling `GC.Collect()` and `GC.WaitForPendingFinalizers()`, which won't guarantee finalizers are called on .NET Core and .NET 5 and later.
BillWagner added a commit that referenced this issue Jun 10, 2021
* update guidance on finalizers

Fixes #17463

This PR does the following:

- Uses the language from the C# standard to define if finalizers are called.
- copy edit the article. Mostly removing any use of "destructor" which is no longer the preferred term.
- Update samples so they don't show the practice of calling `GC.Collect()` and `GC.WaitForPendingFinalizers()`, which won't guarantee finalizers are called on .NET Core and .NET 5 and later.

* update link

Rather than move the entire set of snippets, just update the one that moved.

* respond to feedback.

* Update docs/csharp/programming-guide/classes-and-structs/snippets/destructors/expr-bodied-destructor.cs

Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>

Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
Youssef1313 pushed a commit to Youssef1313/docs that referenced this issue Jul 5, 2021
* update guidance on finalizers

Fixes dotnet#17463

This PR does the following:

- Uses the language from the C# standard to define if finalizers are called.
- copy edit the article. Mostly removing any use of "destructor" which is no longer the preferred term.
- Update samples so they don't show the practice of calling `GC.Collect()` and `GC.WaitForPendingFinalizers()`, which won't guarantee finalizers are called on .NET Core and .NET 5 and later.

* update link

Rather than move the entire set of snippets, just update the one that moved.

* respond to feedback.

* Update docs/csharp/programming-guide/classes-and-structs/snippets/destructors/expr-bodied-destructor.cs

Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>

Co-authored-by: Sam Harwell <sam@tunnelvisionlabs.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.