While working through some issues recently with some legacy code written by someone else I was faced with a horrible case of a shared object that was writing to the file. This was a "custom" logging implementation and opened a file for append, inserted the line, and then closed the file. Well in times of heavy load the system would encounter errors such as "Cannot access ___ because it is being used by another process". So in effect the file was either still open, or the lock was not yet released. This post goes through a bit of the detail on how I resolved the issue.
The main manifestation of this issue was with a series of very aggressive log writes, 100+ calls in a row. So for testing I created an application that would write out 2000+ entries one after another to simulate heavy load. The previous developers had included a "retry" process to get around the issue. If the file couldn't be opened, it pause 10 milliseconds then re-tried. For a 5000 record set the app would average 1800 retries. This process was clunky at best, and involved exception handling at every attempt.
I decided to look at a locking scenario, and came up with the following minimized code.
1: Imports System.IO
2: Imports System.Threading
4: Public Class FileWriteTest
5: Private _filePath As String = "C:\Testing\myfile.txt"
6: Private _fileMutex As New Mutex()
9: Public Sub WriteLine(ByVal sMsg As String)
10: 'Wait until free
12: Dim ofs As FileStream
14: ofs = New FileStream(_filePath, FileMode.Append, _
15: FileAccess.Write, FileShare.None)
16: Dim oFile As New StreamWriter(ofs)
18: Dim sLine As String = sMsg & ControlChars.NewLine
22: Catch e As Exception
24: End Try
26: 'Release the mutex to allow others to call
28: End Try
29: End Sub
30: End Class
Now, this fix was a very simple I created a Mutex inside of the class that actually did the file operations, and then used WaitOne() to ensure that I could be the only one accessing the code, then after all file operations were complete, including the closing of the file, I release the mutex. This removed the need for a continual "retry" method as was previously used and improved performance by over 35%.
Now, the Mutex is a much more severe and costly operation than a SyncLock, but due to the nature of the code that I am working with, I couldn't get a SyncLock to work the way it needed to, thus the Mutex solution.
I hope that this example has helped someone out as I know when I was looking for solutions, I found it VERY hard to find any solution other than a "keep trying" approach, which to me just wasn't an option! Feel free to share comments below, and if you have any specific implementation questions please feel free to post to the forum!