Can anyone explain this behavior at completion

While the “investigation” of the finalization (read: trying stupid), I came across some unexpected behavior (at least for me).

I would expect the Finalize method to not be called, whereas it is called twice

class Program { static void Main(string[] args) { // The MyClass type has a Finalize method defined for it // Creating a MyClass places a reference to obj on the finalization table. var myClass = new MyClass(); // Append another 2 references for myClass onto the finalization table. System.GC.ReRegisterForFinalize(myClass); System.GC.ReRegisterForFinalize(myClass); // There are now 3 references to myClass on the finalization table. System.GC.SuppressFinalize(myClass); System.GC.SuppressFinalize(myClass); System.GC.SuppressFinalize(myClass); // Remove the reference to the object. myClass = null; // Force the GC to collect the object. System.GC.Collect(2, System.GCCollectionMode.Forced); // The first call to obj Finalize method will be discarded but // two calls to Finalize are still performed. System.Console.ReadLine(); } } class MyClass { ~MyClass() { System.Console.WriteLine("Finalise() called"); } } 

Can someone explain if this behavior is intentional, and if so, why?

This code above was compiled in x86 debugging mode and run on CLR v4.

Many thanks

+6
source share
4 answers

I can guess ... and this is really just a guess. According to Eric, do not break rules like this :) This is an assumption only for the sake of idle speculation and interest.

I suspect there are two data structures:

  • Finalization Queue
  • Object Title

When the GC notices that an object has the right to garbage, I suspect that it checks the object header and adds a link to the completion queue. Your SuppressFinalization calls prevent this behavior.

Separately, the stream of the finalizer passes through the finalization queue and calls the finalizer for everything that it finds. Your calls to ReRegisterForFinalize bypassed in the usual way when the link ends in the queue and adds it directly. SuppressFinalization does not remove the link from the queue - it only stops the link to add to the queue in the usual way.

All this explains the behavior that you see (and which I reproduced). This also explains why, when I delete the SuppressFinalization calls, I end up looking at the trigger finalizer, because in this case the “normal” path adds a link to the finalization queue.

+10
source

I do not know what causes strange behavior. However, since you are violating the documented use of the method, anything can happen. The documentation for ReRegisterForFinalize says:

Requests that the system calls the finalizer for the specified object for which SuppressFinalize had previously been called.

You have not previously called SuppressFinalize before you called ReRegisterForFinalize. The documentation does not say what happens in this situation, and in fact, apparently, something is really strange.

Unfortunately, the same documentation page further shows an example in which ReRegisterForFinalize is called on an object for which SuppressFinalize was not called.

This is a bit of a mess. I will take it with the documentation manager.

The moral of the story is, of course, if it hurts when you break the rules described in the documentation and stop breaking them.

+16
source

Interesting data:

  • mono 2.10.8.1 on linux does not call finalizer
  • mono 2.8 on linux does not call finalizer: http://ideone.com/J6pl4
  • mono 2.8.1 on Win32 does not call finalizer
  • mono 2.6.7 on Win32 does not call finalizer

  • .NET 3.5 on Win32 calls finalizer twice

Verification code for reference:

 class Program { static void Main(string[] args) { // The MyClass type has a Finalize method defined for it // Creating a MyClass places a reference to obj on the finalization table. var myClass = new MyClass(); // Append another 2 references for myClass onto the finalization table. System.GC.ReRegisterForFinalize(myClass); System.GC.ReRegisterForFinalize(myClass); // There are now 3 references to myClass on the finalization table. System.GC.SuppressFinalize(myClass); System.GC.SuppressFinalize(myClass); System.GC.SuppressFinalize(myClass); // Remove the reference to the object. myClass = null; // Force the GC to collect the object. System.GC.Collect(2, System.GCCollectionMode.Forced); // The first call to obj Finalize method will be discarded but // two calls to Finalize are still performed. } } class MyClass { ~MyClass() { System.Console.WriteLine("Finalise() called"); } } 
+5
source

I suspect this is within the scope of undefined behavior. If you look at the documentation for ReRegisterForFinalize and SuppressFinalize , they say:

The obj parameter must be the caller for this method.

And this does not apply to your code.

+2
source

Source: https://habr.com/ru/post/900780/


All Articles