1

I just updated to xUnit 2.1. I'm using this method via a lot of tests running in parallels from differents assemblies to log to the same file with a certain name. The problem is I can't manage concurrency (even using TextWriter.Synchronized), every time I run my tests, different tests says:

The error is:

Message: System.IO.IOException : The process cannot access the file 'C:\LOGS\myFileName-20170510.txt' because it is being used by another process.

My method is:

private static void WriteToLogFile(string fileName, string strMessage)
        {
            DateTime utcNow = DateTime.UtcNow;
            lock (fileName)
            {
                using (StreamWriter w = File.AppendText(fileName + utcNow.Year + utcNow.Month.ToString("00") + utcNow.Day.ToString("00") + ".txt"))
                {
                    w.WriteLine("{0};{1};{2}", DateTime.UtcNow, SecurityHelper.GetPrincipalName(), strMessage);
                    w.Flush();
                    w.Close();
                }
            }
        }

I tried even (but this doesn't helped):

private static void WriteToLogFile(string fileName, string strMessage)
{
    DateTime utcNow = DateTime.UtcNow;
    lock (fileName)
    {
        using (StreamWriter w = File.AppendText(fileName + utcNow.Year + utcNow.Month.ToString("00") + utcNow.Day.ToString("00") + ".txt"))
        {
            w.WriteLine("{0};{1};{2}", DateTime.UtcNow, SecurityHelper.GetPrincipalName(), strMessage);
            w.Flush();
            w.Close();
        }
    }
}

private static readonly ConcurrentDictionary<string, object> Tokens = new ConcurrentDictionary<string, object>();

private static object GetToken(string fileName)
{
    if (!Tokens.ContainsKey(fileName))
        Tokens.TryAdd(fileName, new object());
    return Tokens[fileName];
}

This is due to some strange behaviour of xUnit? Thanks in advance.

Cirelli94
  • 1,714
  • 1
  • 15
  • 24
  • 1
    That method for writing into a log file, is this used in productive code or just in test code? – Theo Lenndorff May 10 '17 at 10:47
  • @TheoLenndorff both! – Cirelli94 May 10 '17 at 13:08
  • It does not help answering your question, but if you want to avoid very, very, very much pain in the future ... use a logger library instead of rolling your own logger. Use log4net or nlog. Even System.Diagnostic.Trace is better. Please. For your own sake ... consider it if possible. Every time someone uses File.AppendText (or something similar) as a logging mechanism a kitten dies. And yes ... kittens died because of me, too. I just don't want to see any more dead kittens. Dead kittens everywhere! They are talking to me. They want my soul. NOOOO ... *sanity kicks in* Seriously. Consider it. – Theo Lenndorff May 10 '17 at 14:19

1 Answers1

1

Following line is a problem:

lock (fileName)

Do not use a string and a parameter as lock object. In your case try a private static object. See MSDN and this question on stackoverflow. That would look something like this:

private readonly object Locker = new object();

private static void WriteToLogFile(string fileName, string strMessage)
{
    DateTime utcNow = DateTime.UtcNow;
    lock (Locker)
    {
    ...

This solves the potential problem that multiple threads want to write at once, but that is not the real problem. Have a look at this stackoverflow question. I do not exactly know how the xunit test runner is working, but if there are multiple processes writing at once you'll get that exception. Instead of using File.AppendText use this (as suggested by the answer of stackoverflow question):

using (var stream = File.Open(path, FileMode.Open, FileAccess.Write, FileShare.Read))
{
}

The OS will allow multiple processes accessing the same file.

You might still have following problem: The content might be a mess, because for example two processes might want to write at the same line and then dump two linebreaks consecutively. As a workaround you can append process IDs to the log file.

If this gets too weird consider using a logging library which manages this kind of problem for you.

Community
  • 1
  • 1
Theo Lenndorff
  • 4,556
  • 3
  • 28
  • 43
  • Locker should be `private readonly static object`!? Thank you very much! At the very end, because my logger needs to write on different files (which file depends by the LogType) I used a dictionary: `private static readonly ConcurrentDictionary LockDictionary = new ConcurrentDictionary { }; ` where LogType is an enum, but I think could be even a string. And when locking I did: `lock (LockDictionary[logType]) {using (...)...}` Thanks! – Cirelli94 May 10 '17 at 15:48
  • You are welcome. String can be used for locking, but it is discouraged (see http://stackoverflow.com/questions/12804879/is-it-ok-to-use-a-string-as-a-lock-object). Usually an `object` is used. – Theo Lenndorff May 10 '17 at 18:45