Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing a location string to const breaks my logger class

I've been struggling with an issue that recently popped up with a simple logtofile class I wrote.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

namespace Assets.Code
{
    class TimingLogger
    {
        public static readonly TimingLogger Logger = new TimingLogger();
        private static readonly string path = "C:\\Logs\\TimingLog.txt";
        private readonly Mutex mutex = new Mutex(false, path);
        private StreamWriter writer;
        private readonly Queue<string> queue = new Queue<string>();
        private bool isRunning;
        private readonly object obj = new object();

        private TimingLogger()
        {

        }

        public void CheckPath()
        {
            if (!File.Exists(path))
            {
                File.Create(path);
            }
        }

        public void Run()
        {
            isRunning = true;
            while (isRunning)
            {
                lock (obj)
                {
                    while (queue.Count <= 0)
                    {
                        Monitor.Wait(obj);
                    }
                    Log(queue.Dequeue());
                }
            }
        }

        public void Log(string line)
        {
            try
            {
                mutex.WaitOne();
                writer = File.AppendText(path);
                writer.WriteLine(line);
                writer.Close();
            }
            catch (Exception)
            {
                //throw;
            }
            finally
            {
                mutex.ReleaseMutex();
            }
        }

        public void Enqueue(string line)
        {
            lock (obj)
            {
                queue.Enqueue(line);
                Monitor.Pulse(obj);
            }
        }

        public void Stop()
        {
            isRunning = false;
        }
    }
}

This class was working just fine until recently, when I noticed that my log file wasn't showing the data I expected. Strangely enough, I hadn't changed any functionality of the class. Comparing an old, working version with my new one, the only difference was that some of my fields were made private and readonly. Besides that, the string path was changed to const. And to my complete bewilderment, changing this back to readonly fixed the issue I was having.

So my question is: How on earth is this possible? As far as I'm aware, there shouldn't functionally be any difference between readonly and const in this situation.

When debugging, the change in behavior is substantial, especially in the Run() method. What should happen here is that once Log(queue.Dequeue()); has been called, the thread will leave the lock statement and iterate through the while (isRunning) loop again. This seems pretty obvious, right? However, when I change the string path to const and debug again, the Log(queue.Dequeue()); is passed once and a single statement can be found in the log file, after which it simply doesn't do anything else ever again. It doesn't come past while (isRunning) again and it doesn't seem to leave the lock (obj) block. The logger thread seems to simply shutdown or pause after successfully calling Log(queue.Dequeue()); once.

Actually throwing the exception in the Log method makes no difference, no exceptions are thrown because the logging itself works fine.

I should mention that I'm using this code with Unity3D 5, which uses Mono. But still, this drastic change in behavior by such a small edit seems impossible to me. Can anyone explain why this is happening?

Thanks!

like image 224
David Avatar asked Nov 10 '22 15:11

David


1 Answers

Here is the difference:

Consts are created in the metadata of the files, so when you run you class the value is already there.

ReadOnly are initialized in compile time, in your case, heres the trick, even though you declared path first then the mutex, the compiler initialized the mutex object first, here is why:

Your first static object to be initialized is the Logger:

public static readonly TimingLogger Logger = new TimingLogger();

Because you called the constructor, the non static members are initialized, making mutex the next member to be initialized. At this point you didnt initialized path yet, so you are creating your mutex object with parameters false and null.

If you want to have the same error that you have with const using readonly, you can force the order of your static parameters initialization using a static constructor like :

static TimingLogger()
{
    path = "C:\\Logs\\TimingLog.txt";
    Logger = new TimingLogger();
}

Or simply putting path before Logger.

If you don't want to have the error using const, just change the mutex initialization using null parameter:

private readonly Mutex mutex = new Mutex(false, null);
like image 173
Rodrigo López Avatar answered Nov 14 '22 22:11

Rodrigo López