0

I have a logging class in which I spawn a Task to append all messages from a BlockingCollection<string> to the log file, sleep two seconds, and repeat infinitely, until the fifo is completed.

This works fine for local files, but when writing to a slow network drive I'm getting intermittent failures at using (StreamWriter sw): IOException - The process cannot access the file 'x.txt' because it is being used by another process.

I am not writing to the same file from two threads. There are 3-4 instances of my logger going, but each instance has a unique filename based on timestamp and guid. No external apps are accessing these files at the time.

Even though the StreamWriter is disposed by the using-block in the first trip through the while loop and I sleep for a while, the second trip through the while loop somehow still finds the file to be open at some lower level (The stack trace gets down to System.IO.FileStream.Init, and ultimately, System.IO.__Error.WinIOError.

I can intermittently, sometimes, reproduce the exception in a simple integration test that spins up 100 logger objects in a Parallel.For loop.

        private void FlushFifoToLogFile()
        {
            while (!_fifoLog.IsCompleted)
            {
                if (_fifoLog.Count > 0)
                {
                    using (StreamWriter sw = new StreamWriter(_logFilePath, append:true))
                    {
                        while (_fifoLog.TryTake(out string line))
                        {
                            sw.WriteLine(line);
                        }
                    }
                }
                
                _flushCts.Token.WaitHandle.WaitOne(SLEEP_MILLISECONDS);
            }
        }
4
  • I would avoid this constant writelines. Instead I would append every line in a StringBuilder buffer and then call File.WriteAllLines.
    – Steve
    Commented Jan 10 at 21:10
  • Best way is to add a lock so only one thread goes through the code at one time. There may be some STATIC methods in the library that is giving an issue. See : learn.microsoft.com/en-us/dotnet/csharp/language-reference/…
    – jdweng
    Commented Jan 10 at 21:15
  • 2
    stackoverflow.com/a/6350267/14868997 Intermittent connections can exacerbate the issue of delayed close (which is caused by the OS finishing up the file system writes). Best to just catch it and sleep again. Commented Jan 10 at 21:47
  • @Charlieface Yeah, catch & retry was the direction I was headed, but it would be nice to to have a way to really know what is failing under the hood.
    – amonroejj
    Commented Jan 10 at 22:05

1 Answer 1

1

Per recommendation from @Charlieface, the lowest friction solution is a basic try/catch.

I also incorporated @Steve's StringBuilder suggestion.

        private void FlushFifoToLogFile()
        {
            while (!_fifoLog.IsCompleted)
            {
                if (_fifoLog.Count > 0)
                {
                    try
                    {
                        Encoding utf8NoBom = new UTF8Encoding(false);
                        using (StreamWriter sw = new StreamWriter(_logFilePath, append:true, encoding:utf8NoBom, bufferSize:65536))
                        {
                            StringBuilder sb = new StringBuilder();
                            while (_fifoLog.TryTake(out string line))
                            {
                                sb.AppendLine(line);
                                if (_echoToConsole == true) { Console.WriteLine(line); }
                            }
                            sw.Write(sb.ToString());
                        }
                    }
                    catch (IOException)         // IOException is semi-expected
                    {
                        // Do nothing. Unprocessed items will still be waiting in the fifo for the next attempt.
                    }                            
                }
                
                _flushCts.Token.WaitHandle.WaitOne(SLEEP_MILLISECONDS);
            }
        }
12
  • The lowest friction solution would be to use a logging library like Serilog which already takes care of buffering and writing log messages with minimal overhead Commented Feb 28 at 18:27
  • Sometimes, depending on whether your environment/culture supports this. There could also be non-log tasks that write to network drives that this question/answer might help someone with in the future.
    – amonroejj
    Commented Feb 28 at 18:47
  • whether your environment/culture supports this it does, otherwise it will be impossible to work with ANY .NET Core version - which includes .NET 5 and later. .NET Core itself uses ILogger everywhere and so do all worthwhile NuGet packages. Saying you can't use logging is close enough to saying you can't use .NET at all. And if the argument is "we can only use what's in the SDK" then you can't even use .NET Framework, because many core libraries are only available through NuGet Commented Feb 29 at 8:02
  • By "environment/culture" I'm referring to employer policies, not a .NET environment.
    – amonroejj
    Commented Feb 29 at 18:12
  • That's what I refer to as well. No company policy can mandate where the sun rises from or Microsoft's .NET works. If you want to use either .NET Framework or .NET Core you have to use NuGet packages. If you want security, it's infinitely better to use a tested library than building your own and having to find all the problems yourself. This code leaks strings and Encoding objects, and pays a big overhead by opening the stream over and over. You'd avoid the IO problem completely if you opened the file only once. Commented Mar 1 at 7:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.