ChatGPT解决这个技术问题 Extra ChatGPT

How does a garbage collector avoid an infinite loop here?

Consider the following C# program, I submitted it on codegolf as an answer to create a loop without looping:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

This program looks like an infinite loop in my inspection, but it seems to run for several thousand iterations, and then the program terminates successfully without error (No errors are thrown). Is it a spec violation that the finalizer for P eventually is not called?

Clearly this is stupid code, that should never appear, but I am curious as to how the program could ever complete.

Original code golf post:: https://codegolf.stackexchange.com/questions/33196/loop-without-looping/33218#33218

I'm afraid to run this.
That a finalizer isn't called is certainly within the realm of valid behavior. I don't know why it bothers to run several thousand iterations though, I'd expect zero invocations.
The CLR has protection against the finalizer thread never being able to finish its job. It forcefully terminates it after 2 seconds.
So the real answer to your question in the title is that it avoids it by just letting the infinite loop run for 40 seconds and then it is terminated.
From trying it, it seems that the program just kills everything after 2 seconds no matter what. Actually if you keep spawning threads it'll last quite a bit longer :)

E
Eric Scherrer

As per Richter in the second edition of CLR via C# (yes I need to update):

Page 478

For (The CLR is shutting down) each Finalize method is given approximately two seconds to return. If a Finalize method doesn't return within two seconds, the CLR just kills the process - no more Finalize methods are called. Also, if it takes more then 40 seconds to call all objects' Finalize methods, again, the CLR just kills the process.

Also, as Servy mentions, it has its own thread.


Each finalize method in this code takes dramatically under 40 seconds per object. That a new object is created and then eligible for finalization is not relevant to the current finalizer.
This isn't actually what gets the job done. There's also a timeout on the freachable queue getting emptied at shutdown. Which is what this code fails on, it keeps adding new objects to that queue.
Just thinking about this, isn't emptying the freachable queue the same as "Also, if it takes more then 40 seconds to call all objects' Finalize methods, again, the CLR just kills the process."
N
Nate Cook

The finalizer doesn't run in the main thread. The finalizer has its own thread that runs code, and it's not a foreground thread that would keep the application running. The main thread completes effectively right away, at which point the finalizer thread simply runs as many times as it gets a chance to before the process gets torn down. Nothing is keeping the program alive.


If the finalizer hasn't completed 40 seconds after the program should've exited due to no main thread being alive, it will be terminated and the process will terminate. These are old values though so Microsoft might have tweaked the actual numbers or even the whole algorithm by now. Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
@LasseV.Karlsen Is that documented behavior of the language, or simply how MS chose to implement their finalizers as an implementation detail? I would expect the latter.
I expect the latter as well. The most official reference to this behavior I've seen is what Eric above posted in his answer, from the book CLR via C# by Jeffrey Richter.
W
Willem Van Onsem

A garbage collector is not an active system. It runs "sometimes" and mostly on demand (for instance when all pages offered by the OS are full).

Most garbage collectors run in a breadth-first generation-like manner in a subthread. In most cases it can take hours before the object is recycled.

The only problem occurs when you want to terminate the program. However that's not really a problem. When you use kill an OS will ask politely to terminate processes. When the process however remains active, one can use kill -9 where the Operating System removes all control.

When I ran your code in the interactive csharp environment, I've got:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

Thus your program crashes because stdout is blocked by the termintation of the environment.

When removing the Console.WriteLine and killing the program. It after five second the program terminates (in other words, the garbage collector gives up and simply will free all memory without taking finalizers into account).


This is fascinating that the interactive csharp blows up for entirely different reasons. The original program snippet didn't have the console writeline, I'm curious if it would terminate too.
@MichaelB: I've tested this as well (see comment below). It waits for five second and then terminates. I guess the finalizer of the first P instance simply timed out.