7

I have a logger class thats a singleton. In it's destructor I call Close() which prints the footer for the log and then closes the StreamWriter.

 public void Close()
    {
        WriteLogFileFooter();

        _logFile.Flush();

        _logFile.Close();
    }

The problem is when System.Enviornment.Exit(1) is called from elsewhere in the program (portions that I didn't write myself), the footer is never printed and my logger throws an exception for trying to write to a closed stream. I can only assume the Exit command is causing my StreamWriter to be closed before my Singleton is destructed. I tried to use GC.SupressFinalize() on my StreamWriter but that didn't seem to help.

Eric J.
  • 147,927
  • 63
  • 340
  • 553
ForeverNoobie
  • 531
  • 5
  • 17
  • 1
    is your singleton implementing idisposable, and the Dispose from that implementation is what calls your Close class? – Phil Jul 05 '12 at 16:55
  • 6
    In short, you can't do this. You can't access a managed object (such as a stream) from a finalizer, there is no guarentee as to what state it will be in, or if it will even be there at all. – roken Jul 05 '12 at 16:56
  • 2
    I've always wondered why people reinvent wheels. Why don't people use what's built into the framework to perform logging and tracing? I am not even mentioning third party tools. I am just wondering why people don't use what already exists in the framework. Out of curiosity, that's all. – Darin Dimitrov Jul 05 '12 at 16:56
  • @DarinDimitrov I'm to .net so I don't know everything thats available. – ForeverNoobie Jul 05 '12 at 17:00
  • 2
    Then I invite you to check-out the [`System.Diagnostics.Trace`](http://msdn.microsoft.com/en-us/library/system.diagnostics.trace.aspx) class. – Darin Dimitrov Jul 05 '12 at 17:01
  • @Phil nope. Not implementing idisposable. Close() is called from ~Logger(). – ForeverNoobie Jul 05 '12 at 17:03
  • 1
    Along with what everyone said, it is not even safe to call `Console.WriteLine` from the finalizer. – leppie Jul 05 '12 at 17:10

5 Answers5

13

You are violating one explicit rule for finalizers:

The Finalize method should not reference any other objects.

http://msdn.microsoft.com/en-us/library/b1yfkh5e(v=VS.90).aspx

It's entirely possible that the managed object you hold a reference to is collected before your object is collected when the application exits.

UPDATE

If you need to clean up managed resources when the application exits, you could hook up the ProcessExit event of AppDomain rather than rely on non-deterministic behavior of finalizer execution.

.NET Console Application Exit Event

Community
  • 1
  • 1
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • Your quote is out of context. "An object's Finalize method should free any external resources that the object owns. Moreover, a Finalize method should release only resources that the object has held onto. The Finalize method should not reference any other objects." – Trisped Jul 05 '12 at 17:08
  • 2
    It's entirely in context. It should release resources (*unmanaged* resources are meant) *and* not hold references to [*managed*] objects. Nothing about the portion I quoted makes the answer inaccurate. – Eric J. Jul 05 '12 at 17:10
  • @EricJ. so in short how do I write my log footer before the StreamWriter is reclaimed? – ForeverNoobie Jul 05 '12 at 17:18
  • @Eric J. So by your logic no class variables could be accessed in the destructor. Makes it kind of hard to free external resources then. – Trisped Jul 05 '12 at 18:57
  • @Trisped: Certainly you can access class variables, *unless* they are *managed* objects. The intent of the finalizer is to clean up *unmanaged* resources. *Managed* resources are cleaned up by the GC. Not my logic... just quoting from MSDN (and adding a little context). – Eric J. Jul 05 '12 at 19:00
  • @ Eric J. It seems you are not understanding my statement. Your quote of the documentation indicates that you cannot use any other objects. All class properties are derived from System.Object, even int. As such, if you have an object declared as a variable (like an `IntPtr`) it cannot be accessed, according to your quote. When the appropriate context is applied the statement "..any other objects." is quantified to mean any resource the object has not held onto. – Trisped Jul 05 '12 at 19:15
3

You should make your logger implement IDisposable, and use it in a using block. This means that it will be deterministically disposed, whereas now it is being nondeterministically destructed.

The cause of the error is that your stream will sometimes be closed before the logger, as Exit basically destroys everything (nondeterministically) and quits. You should use a deterministic pattern (IDisposable) to avoid this.

In reality, destructors are rarely useful in C#, for the precise reason that they are non-deterministic. They only are worth using for releasing unmanaged resources.

Also, implementing IDisposable may make it inconvenient to use a singleton. I personally think it's better to create an instance to be used throughout the program and disposed at the end, rather than an explicit singleton.

Kendall Frey
  • 43,130
  • 20
  • 110
  • 148
  • 1
    It doesn't make sense to use a using block with a singleton logger. You don't want to dispose it after each use. – roken Jul 05 '12 at 17:00
  • 2
    That also does not answer the OP's question. – Eric J. Jul 05 '12 at 17:00
  • The problem is I want to use the logger from multiple classes without creating more than one instance. Also Exit() is being called in main() but not at the end as you might expect. It's being called in response to an error. So even if I had a using block encasing my main function I'd be getting the same error I think – ForeverNoobie Jul 05 '12 at 17:13
  • @user1379635: I think a `return;` would be a better way to go, since that allows you to use `IDisposable`, whereas `Exit` is not designed for that. Also, you could use the singleton pattern, and `Dispose` the singleton in a `finally` block at the end of the program. – Kendall Frey Jul 05 '12 at 17:16
1

As others have already clearly stated, you should not attempt to access your _logFile object at all from your logger class' finalizer. You shouldn't access any other objects in a finalizer, because the Garbage Collector might already have wiped them out of existence.

I think you could avoid your problem by a few simple steps:

  1. Get rid of your current finalizer.

  2. Perform a _logFile.Flush after every single write, instead of waiting until the very end of your logger object's lifetime when it might already be too late.

    Flushing a log file stream frequently seems legitimate to me because the whole point of having a log is using it to find, and deal with, situations where errors have occurred. If your process is suddenly terminated by an exceptional situation, your log should still be as complete as possible; thus flushing the log stream buffer frequently seems a sensible thing to do.

  3. Make your logger implement IDisposable (this MSDN Magazine article will explain to you how this is done) and close your log file stream from there.

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
0

I had the same problems and my solution was as follows:

  1. When creating the FileStream in the constructor of your class used GC.SuppressFinalize immediately. This makes you responsible for cleaning the stream
  2. Close the stream in the Dispose() of the class
public class LogFileEventListener : IDisposable
{
    private bool disposed = false;
    private FileStream fileStream;

    public LogFileEventListener(string path)
    {
        //Opens a new file stream to log file
        this.fileStream = new FileStream(path, FileMode.Append, FileAccess.Write);
        GC.SuppressFinalize(this.fileStream);
    }

    /// <summary>Finalize the listener</summary>
    ~LogFileEventListener() { this.Dispose(); }

    /// <summary>Disposes the listener</summary>
    public override void Dispose()
    {
        try
        {
            if (!this.disposed)
            {
                /* Do you stuff */

                //Close the log file
                if (this.fileStream != null)
                {
                    this.fileStream.Close();
                    this.fileStream = null;
                }

                base.Dispose();
            }
        }
        finally
        {
            this.disposed = true;
            GC.SuppressFinalize(this);
        }
    }
}
Ivan Ferić
  • 4,725
  • 11
  • 37
  • 47
Roel van Megen
  • 341
  • 3
  • 8
-2

Most likely the StreamWriter is being closed else where. Try creating an additional StreamWriter in your singleton's constructor, write to it a few times (to confirm that it is working), then write to it again in the destructor before calling close (close will also flush).

If the above works then you will know some other code is closing your log. If it does not work then you will know that it is a .NET thing (possibly having something to do with how/where the variable is referenced).

According to the documentation, you should be able to work around the issue by putting the StreamWriter in a base class. This of course will not work for you because your test case is not a standard finalization, but is a program exit, meaning .NET does what it wants when it wants. Instead, you should catch the exit event, dispose of this class, then return, to guarantee that things are disposed in the correct order. You should also check if the StreamWriter is already closed in the finalizer in case the program aborts due to an error.

Trisped
  • 5,705
  • 2
  • 45
  • 58
  • 1
    The issue is well identified in the comments on the question, and this is not it. – roken Jul 05 '12 at 17:04
  • @roken You are entitled to your opinion, but my experience with .NET contradicts your assumptions on the issue. – Trisped Jul 05 '12 at 17:11
  • @Trisped roken is right this time. There's no way anything could be closing my streamwriter besides the garbage collector. I just now wrote the logging class and no outside code really knows about it yet. Thanks anyway. – ForeverNoobie Jul 05 '12 at 17:16
  • @Trisped In some sense your answer is accurate, it *is* being closed elsewhere, but this is certainly due to the fact that the stream is attempting to be accessed in the finalizer. The GC is more than likely finalizing the stream (thus closing it) before finalizing the logger. It isn't my opinion, it is fact that you cannot use the stream in the finalizer. There is no contradiction here. – roken Jul 05 '12 at 17:17
  • @roken That he cannot use the stream is clear, though why is still not (to me at least). The destructor must allow cleanup of unmanaged resources. As such this means access to local class variables. It does not make sense that an object in the class would be finalized BEFORE the references to it are removed/terminated. – Trisped Jul 05 '12 at 18:38
  • @Trisped "Managed" means any object who's memory is managed by .NET. "Unmanaged" does not mean "local class variable" (or object field, if you will). Your statements in this last comment are completely incorrect. Finalization is *not* deterministic and as such it makes perfect sense that the stream is finalized prior to the logger. Please review all the other comments and answers to this question as they provide futher detail explaining this. – roken Jul 05 '12 at 18:52
  • @roken to cleanup unmanaged resources may (and often does) require access to managed resources. http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx states "The garbage collector checks for objects that are no longer being used by the application. If it considers an object eligible for destruction, it calls the destructor (if any) and reclaims the memory used to store the object." But the class variable for the `StreamWriter` is still in use as the class which created the reference still exists. I will accept that you do not know the answer and let this be. – Trisped Jul 05 '12 at 19:21
  • @Trisped I'm not trying to argue with you, I simply wish for you to be properly informed. Also note that your answer is -1 while my comment on the question is +6. If a managed object holds unmanaged resources, that managed object is responsible for freeing the resource. In this scenario, the logger IS NOT responsible for freeing the unmanaged resources held by the StreamWriter. The StreamWriter class implements the finalize/dispose pattern to ensure that when it is collected, it will free the file handle. Your link also mentions finalizers "run on shutdown." Order is not guarenteed here. – roken Jul 05 '12 at 21:04
  • @roken It seems to be an argument, as the reasons/evidence you are providing do not answer the issue. The point about -1 vs +6 seems pointless. These votes do not guarantee accuraccy, as proven by the fact that your comment is not 100% correct yet has +6. My question is why the `StreamWriter` was closed out of order, which could be answered with a link to documentation or blog on MS site which details what you can and cannot access and why. Since you have not provided this or documentation on finalizing on exit, I accept that you do not know the answer and will let the issue be. – Trisped Jul 05 '12 at 22:14