Why my Close function isn't called?
Asked Answered
P

3

10
 class Program : CriticalFinalizerObject
    {
        static void Main(string[] args)
        {

            Program p = new Program();
            TextWriterTraceListener listener = new TextWriterTraceListener(@"C:\trace.txt");
            Trace.Listeners.Clear(); // Remove default trace listener
            Trace.Listeners.Add(listener);
            Trace.WriteLine("First Trace"); // Generate some trace messages
            Trace.WriteLine("Perhaps last Trace.");

        }

        ~Program()
        {
            Trace.Close();
        }
    }

I get file size =0

the finilizer should have executed because I derive from CriticalFinalizerObject

I don't want to use Trace.Close() not in the Finalizer.

edit

after @eric Lippert reply : Ive re-edited the code trying to match it to :constrained execution region ( but still no success)

  [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    class Program : CriticalFinalizerObject
    {

        static void Main(string[] args)
        {
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
            }
            catch (Exception e)
            {
            }
            finally
            {
                Program p = new Program();
                TextWriterTraceListener listener = new TextWriterTraceListener(@"C:\trace1.txt");
                Trace.Listeners.Clear();
                Trace.Listeners.Add(listener);
                Trace.WriteLine("First Trace");
                Trace.WriteLine("Perhaps last Trace.");
            }
        }

        ~Program()
        {
            Trace.Flush();
        }
    }
Piacular answered 6/4, 2012 at 13:16 Comment(15)
@Tigran still size 0.... try it....Piacular
I just suppose that finalizer is not even called cause the program is gonna be closed.Naturalist
@Naturalist thats why CriticalFinalizerObject is there.... to make sure it is called....Piacular
Again, I suppose this one is a particular case, or better a case with particular behavior. Cause you gonna act on the class that holds actually the main entrance to your app.Naturalist
@Naturalist so dont ask it in interview question... :) it has unpredictible results....Piacular
@Roti Namir: Mine are just suspects, in yuor pants, I would just wait someone from .NET/CLR team to find your question and give you valuable responce. I, instead, mark it like preferred to follow the discussion.:)Naturalist
Try adding a call to Trace.Flush() in your main method. If I am understanding the docs correctly, Trace.Close may not call flush on the underlying listener.Linnealinnean
@Naturalist yeah I guess i'll have to wait for true CLR answer.Piacular
@Linnealinnean ive changed close to flush , still isnt working in destructor....Piacular
Don't change Close to Flush, call Trace.Flush in the main. That way the data is definitely flushed before the Close call.Linnealinnean
@RoyiNamir In my system the finiliser is getting called, I put a breakpoint in there and see the execution steps. But even after executing Trace.Close, the file is not saved! Seems like an issue of Trace.Close getting executed in a finiliser thread.Lipkin
@Linnealinnean Are you aware that this question is why it is not called in the finilizer?Piacular
Yes, but you are saying it is "not called" because your file is zero bytes, it could be the file is zero bytes because the listener has already been finalized (as I am pretty sure that is not a critical finalize object). I was going on the assumption that this may have been called, but since the data is not flushed, you get a 0 byte file.Linnealinnean
@RoyiNamir What has your Code Analysis/FXCop Analysis told you about your code?Lantern
I don't want to use Trace.Close() not in the Finalizer. reminds me of youtube.com/watch?v=XIX0ZDqDljALantern
E
11

As the documentation clearly states:

In classes derived from the CriticalFinalizerObject class, the common language runtime (CLR) guarantees that all critical finalization code will be given the opportunity to execute, provided the finalizer follows the rules for a CER, even in situations where the CLR forcibly unloads an application domain or aborts a thread. If a finalizer violates the rules for a CER, it might not successfully execute

Does your finalizer follow all the rules for a constrained execution region?

UPDATE:

You've updated your code in an attempt to make it follow the rules of constrained execution regions, but I see no evidence whatsoever that you've done so correctly. The rules are quite clear; a finalizer in a constrained execution region absolutely must not do any of these things:

  • allocate memory
  • box a value type
  • acquire a lock
  • call an arbitrary virtual method
  • call any method which lacks a reliability contract

Does your finalizer do any of those five things? If it does then there is no requirement that the CLR honours your desire for the finalizer to always run.

Moreover: forget about constrained execution regions for a moment because your program as it stands now is not even threadsafe. You've written a nasty race condition into the program.

What stops the jitter from garbage-collecting p before the trace starts? Nothing! The jitter knows that p will never be used again, and is perfectly within its rights to collect it immediately after its allocation. The flush could be happening at any time on the finalizer thread, including before the trace writes happen, or in the middle of any of them.

Ell answered 6/4, 2012 at 13:53 Comment(3)
You should be telling us the answer to that, Eric, if you want this to be an answer... ;-)Frisian
Hint: It does not. Consider reading the CER rules: msdn.microsoft.com/en-us/library/ms228973.aspx. Then consider evaluating the rules (there's a convenient bullet list). In particular what is the ReliabilityContract on Trace.Flush? Perhaps you're using a finalizer/CER for the wrong thing.Lantern
ProTip: Writing reliable (CER-related) code is challenging. If you don't fully understand the tools in the solution domain, you'll probably screw it up.Lantern
W
10

Because you didn't create instance of Program class.

You can read more here:

This method is automatically called after an object becomes inaccessible, unless the object has been exempted from finalization by a call to SuppressFinalize. During shutdown of an application domain, Finalize is automatically called on objects that are not exempt from finalization, even those that are still accessible. Finalize is automatically called only once on a given instance, unless the object is re-registered using a mechanism such as ReRegisterForFinalize and GC.SuppressFinalize has not been subsequently called.

So, you need to have instance of object, if you want finilizer to be called.

UPDATE: Consider using Trace.AutoFlush = true; if you want message to be written.

UPDATE: (Why Close function isn't called?) Actually you Close function is called (if nothing exceptional happens in other finalizers). If you will keep default TraceListener (remove Trace.Listeners.Clear() call), you'll see that all strings successfully written to Output window.

Problem here is that StreamWriter (which is created inside TextWriterTraceListener) does not has finalizer. So it does not flush all data to file. What you need to do:

FileStream file = new FileStream(@"C:\trace.txt", FileMode.OpenOrCreate);
StreamWriter writer = new StreamWriter(file);
GC.SuppressFinalize(file);
GC.SuppressFinalize(file.SafeFileHandle);
var listener = new TextWriterTraceListener(writer);

Actually you will need to close file manually on your finalizer.

Walkup answered 6/4, 2012 at 13:18 Comment(3)
+1. I feel really dumb for not seeing that right off the bat. Good catch.Hyperbolic
@RoyiNamir: Can you please update your code to show the instantiation of the Program class so that everybody else doesn't come to this same conclusion?Blaeberry
the whole question is my travel through learning GC internals...:) So i'd rather not use Trace.AutoFlush but to learn WHY it is not called.Piacular
S
0

In C# you can't be certain when or that your finalizer is called, because the GC handles that. So if you have some resources to release, it's best to create a class which implements IDisposable and use it like this:

using (MyResourceHolder rh = new MyResourceHolder()) {
    // ...
} // rh.Dispose() is called implicitly

In the Dispose-method you can call Trace.Close().

See http://msdn.microsoft.com/en-us/library/system.idisposable.aspx for a good implementation of IDisposable.

Session answered 6/4, 2012 at 13:38 Comment(2)
Jasd , did you read the threads ? :) the whole question is why it is not being called. I know the dispose pattern and other solutions. thanks.Piacular
Yeah, I read it, and I told you that you can't be sure that the finalizer is called, maybe sometimes it is, sometimes it is not. So you shouldn't use it at all for such purposes.Session

© 2022 - 2024 — McMap. All rights reserved.